The long-awaited config box revamp! I've taken the whole config box
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Wed, 5 Mar 2003 22:07:40 +0000 (22:07 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Wed, 5 Mar 2003 22:07:40 +0000 (22:07 +0000)
to pieces, and put it back together in a new table-driven form.
config.c sets up a data structure describing most of the config box;
wincfg.c adds in the Windows-specific options (so that config.c can
also form the basis for Mac and Unix config boxes). Then winctrls.c
contains a shiny new layout engine which consumes that data
structure, and windlg.c passes all WM_COMMAND and similar messages
to a driver alongside that layout engine. In the process I've sorted
out nicer-looking panel titles and finally fixed the list-boxes-are-
never-the-right-size bug (turned out to be Windows's fault, of
course). I _believe_ it should do everything the old config box did,
including context help. Now everyone has to test it thoroughly...

git-svn-id: svn://svn.tartarus.org/sgt/putty@2908 cda61777-01e9-0310-a592-d414129be87e

Recipe
config.c [new file with mode: 0644]
dialog.c [new file with mode: 0644]
dialog.h [new file with mode: 0644]
doc/config.but
win_res.rc
wincfg.c [new file with mode: 0644]
winctrls.c
windlg.c
winhelp.h [new file with mode: 0644]
winstuff.h

diff --git a/Recipe b/Recipe
index 07bd56e..08399e4 100644 (file)
--- a/Recipe
+++ b/Recipe
@@ -95,7 +95,7 @@
 
 # GUI front end and terminal emulator (putty, puttytel).
 GUITERM  = window windlg winctrls terminal sizetip wcwidth unicode ldiscucs
-         + logging printing winutils
+         + logging printing winutils dialog config wincfg tree234
 
 # Non-SSH back ends (putty, puttytel, plink).
 NONSSH   = telnet raw rlogin ldisc
@@ -146,7 +146,7 @@ pageant  : [G] pageant sshrsa sshpubk sshdes sshbn sshmd5 version tree234
 
 puttygen : [G] puttygen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
          + sshrand noise sshsha winstore misc winctrls sshrsa sshdss winmisc
-         + sshpubk sshaes sshsh512 import winutils puttygen.res LIBS
+         + sshpubk sshaes sshsh512 import winutils puttygen.res tree234 LIBS
 
 pterm    : [X] pterm terminal wcwidth uxucs uxmisc tree234 misc ldisc ldiscucs
          + logging uxprint settings pty be_none uxstore signal CHARSET
diff --git a/config.c b/config.c
new file mode 100644 (file)
index 0000000..ebcd607
--- /dev/null
+++ b/config.c
@@ -0,0 +1,1526 @@
+/*
+ * config.c - the platform-independent parts of the PuTTY
+ * configuration box.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "dialog.h"
+#include "storage.h"
+
+#define PRINTER_DISABLED_STRING "None (printing disabled)"
+
+static void protocolbuttons_handler(union control *ctrl, void *dlg,
+                                   void *data, int event)
+{
+    int button, defport;
+    Config *cfg = (Config *)data;
+    /*
+     * This function works just like the standard radio-button
+     * handler, except that it also has to change the setting of
+     * the port box. We expect the context parameter to point at
+     * the `union control' structure for the port box.
+     */
+    if (event == EVENT_REFRESH) {
+       for (button = 0; button < ctrl->radio.nbuttons; button++)
+           if (cfg->protocol == ctrl->radio.buttondata[button].i)
+               break;
+       /* We expected that `break' to happen, in all circumstances. */
+       assert(button < ctrl->radio.nbuttons);
+       dlg_radiobutton_set(ctrl, dlg, button);
+    } else if (event == EVENT_VALCHANGE) {
+       int oldproto = cfg->protocol;
+       button = dlg_radiobutton_get(ctrl, dlg);
+       assert(button >= 0 && button < ctrl->radio.nbuttons);
+       cfg->protocol = ctrl->radio.buttondata[button].i;
+       if (oldproto != cfg->protocol) {
+           defport = -1;
+           switch (cfg->protocol) {
+             case PROT_SSH: defport = 22; break;
+             case PROT_TELNET: defport = 23; break;
+             case PROT_RLOGIN: defport = 513; break;
+           }
+           if (defport > 0 && cfg->port != defport) {
+               cfg->port = defport;
+               dlg_refresh((union control *)ctrl->radio.context.p, dlg);
+           }
+       }
+    }
+}
+
+static void numeric_keypad_handler(union control *ctrl, void *dlg,
+                                  void *data, int event)
+{
+    int button;
+    Config *cfg = (Config *)data;
+    /*
+     * This function works much like the standard radio button
+     * handler, but it has to handle two fields in Config.
+     */
+    if (event == EVENT_REFRESH) {
+       if (cfg->nethack_keypad)
+           button = 2;
+       else if (cfg->app_keypad)
+           button = 1;
+       else
+           button = 0;
+       assert(button < ctrl->radio.nbuttons);
+       dlg_radiobutton_set(ctrl, dlg, button);
+    } else if (event == EVENT_VALCHANGE) {
+       button = dlg_radiobutton_get(ctrl, dlg);
+       assert(button >= 0 && button < ctrl->radio.nbuttons);
+       if (button == 2) {
+           cfg->app_keypad = FALSE;
+           cfg->nethack_keypad = TRUE;
+       } else {
+           cfg->app_keypad = (button != 0);
+           cfg->nethack_keypad = FALSE;
+       }
+    }
+}
+
+static void cipherlist_handler(union control *ctrl, void *dlg,
+                              void *data, int event)
+{
+    Config *cfg = (Config *)data;
+    if (event == EVENT_REFRESH) {
+       int i;
+
+       static const struct { char *s; int c; } ciphers[] = {
+           { "3DES",                   CIPHER_3DES },
+           { "Blowfish",               CIPHER_BLOWFISH },
+           { "DES",                    CIPHER_DES },
+           { "AES (SSH 2 only)",       CIPHER_AES },
+           { "-- warn below here --",  CIPHER_WARN }
+       };
+
+       /* Set up the "selected ciphers" box. */
+       /* (cipherlist assumed to contain all ciphers) */
+       dlg_update_start(ctrl, dlg);
+       dlg_listbox_clear(ctrl, dlg);
+       for (i = 0; i < CIPHER_MAX; i++) {
+           int c = cfg->ssh_cipherlist[i];
+           int j;
+           char *cstr = NULL;
+           for (j = 0; j < (sizeof ciphers) / (sizeof ciphers[0]); j++) {
+               if (ciphers[j].c == c) {
+                   cstr = ciphers[j].s;
+                   break;
+               }
+           }
+           dlg_listbox_addwithindex(ctrl, dlg, cstr, c);
+       }
+       dlg_update_done(ctrl, dlg);
+
+    } else if (event == EVENT_VALCHANGE) {
+       int i;
+
+       /* Update array to match the list box. */
+       for (i=0; i < CIPHER_MAX; i++)
+           cfg->ssh_cipherlist[i] = dlg_listbox_getid(ctrl, dlg, i);
+
+    }
+}
+
+static void printerbox_handler(union control *ctrl, void *dlg,
+                              void *data, int event)
+{
+    Config *cfg = (Config *)data;
+    if (event == EVENT_REFRESH) {
+       int nprinters, i;
+       printer_enum *pe;
+
+       dlg_update_start(ctrl, dlg);
+       dlg_listbox_clear(ctrl, dlg);
+       dlg_listbox_add(ctrl, dlg, PRINTER_DISABLED_STRING);
+       pe = printer_start_enum(&nprinters);
+       for (i = 0; i < nprinters; i++)
+           dlg_listbox_add(ctrl, dlg, printer_get_name(pe, i));
+       printer_finish_enum(pe);
+       dlg_editbox_set(ctrl, dlg,
+                       (*cfg->printer ? cfg->printer :
+                        PRINTER_DISABLED_STRING));
+       dlg_update_done(ctrl, dlg);
+    } else if (event == EVENT_VALCHANGE) {
+       dlg_editbox_get(ctrl, dlg, cfg->printer, sizeof(cfg->printer));
+       if (!strcmp(cfg->printer, PRINTER_DISABLED_STRING))
+           *cfg->printer = '\0';
+    }
+}
+
+static void codepage_handler(union control *ctrl, void *dlg,
+                            void *data, int event)
+{
+    Config *cfg = (Config *)data;
+    if (event == EVENT_REFRESH) {
+       int i;
+       char *cp;
+       dlg_update_start(ctrl, dlg);
+       strcpy(cfg->line_codepage,
+              cp_name(decode_codepage(cfg->line_codepage)));
+       dlg_listbox_clear(ctrl, dlg);
+       for (i = 0; (cp = cp_enumerate(i)) != NULL; i++)
+           dlg_listbox_add(ctrl, dlg, cp);
+       dlg_editbox_set(ctrl, dlg, cfg->line_codepage);
+       dlg_update_done(ctrl, dlg);
+    } else if (event == EVENT_VALCHANGE) {
+       dlg_editbox_get(ctrl, dlg, cfg->line_codepage,
+                       sizeof(cfg->line_codepage));
+       strcpy(cfg->line_codepage,
+              cp_name(decode_codepage(cfg->line_codepage)));
+    }
+}
+
+static void sshbug_handler(union control *ctrl, void *dlg,
+                          void *data, int event)
+{
+    if (event == EVENT_REFRESH) {
+       dlg_update_start(ctrl, dlg);
+       dlg_listbox_clear(ctrl, dlg);
+       dlg_listbox_addwithindex(ctrl, dlg, "Auto", AUTO);
+       dlg_listbox_addwithindex(ctrl, dlg, "Off", FORCE_OFF);
+       dlg_listbox_addwithindex(ctrl, dlg, "On", FORCE_ON);
+       switch (*(int *)ATOFFSET(data, ctrl->listbox.context.i)) {
+         case AUTO:      dlg_listbox_select(ctrl, dlg, 0); break;
+         case FORCE_OFF: dlg_listbox_select(ctrl, dlg, 1); break;
+         case FORCE_ON:  dlg_listbox_select(ctrl, dlg, 2); break;
+       }
+       dlg_update_done(ctrl, dlg);
+    } else if (event == EVENT_SELCHANGE) {
+       int i = dlg_listbox_index(ctrl, dlg);
+       if (i < 0)
+           i = AUTO;
+       else
+           i = dlg_listbox_getid(ctrl, dlg, i);
+       *(int *)ATOFFSET(data, ctrl->listbox.context.i) = i;
+    }
+}
+
+struct sessionsaver_data {
+    union control *editbox, *listbox, *loadbutton, *savebutton, *delbutton;
+    union control *okbutton, *cancelbutton;
+    char savedsession[2048];
+    struct sesslist *sesslist;
+};
+
+/* 
+ * Helper function to load the session selected in the list box, if
+ * any, as this is done in more than one place below. Returns 0 for
+ * failure.
+ */
+static int load_selected_session(struct sessionsaver_data *ssd,
+                                void *dlg, Config *cfg)
+{
+    int i = dlg_listbox_index(ssd->listbox, dlg);
+    int isdef;
+    if (i < 0) {
+       dlg_beep(dlg);
+       return 0;
+    }
+    isdef = !strcmp(ssd->sesslist->sessions[i], "Default Settings");
+    load_settings(ssd->sesslist->sessions[i], !isdef, cfg);
+    if (!isdef) {
+       strncpy(ssd->savedsession, ssd->sesslist->sessions[i],
+               sizeof(ssd->savedsession));
+       ssd->savedsession[sizeof(ssd->savedsession)-1] = '\0';
+    } else {
+       ssd->savedsession[0] = '\0';
+    }
+    dlg_refresh(NULL, dlg);
+    /* Restore the selection, which might have been clobbered by
+     * changing the value of the edit box. */
+    dlg_listbox_select(ssd->listbox, dlg, i);
+    return 1;
+}
+
+static void sessionsaver_handler(union control *ctrl, void *dlg,
+                                void *data, int event)
+{
+    Config *cfg = (Config *)data;
+    struct sessionsaver_data *ssd =
+       (struct sessionsaver_data *)ctrl->generic.context.p;
+
+    if (event == EVENT_REFRESH) {
+       if (ctrl == ssd->editbox) {
+           dlg_editbox_set(ctrl, dlg, ssd->savedsession);
+       } else if (ctrl == ssd->listbox) {
+           int i;
+           dlg_update_start(ctrl, dlg);
+           dlg_listbox_clear(ctrl, dlg);
+           for (i = 0; i < ssd->sesslist->nsessions; i++)
+               dlg_listbox_add(ctrl, dlg, ssd->sesslist->sessions[i]);
+           dlg_update_done(ctrl, dlg);
+       }
+    } else if (event == EVENT_VALCHANGE) {
+       if (ctrl == ssd->editbox) {
+           dlg_editbox_get(ctrl, dlg, ssd->savedsession,
+                           sizeof(ssd->savedsession));
+       }
+    } else if (event == EVENT_ACTION) {
+       if (ctrl == ssd->listbox || ctrl == ssd->loadbutton) {
+           /*
+            * The user has double-clicked a session, or hit Load.
+            * We must load the selected session, and then
+            * terminate the configuration dialog _if_ there was a
+            * double-click on the list box _and_ that session
+            * contains a hostname.
+            */
+           if (load_selected_session(ssd, dlg, cfg) &&
+               (ctrl == ssd->listbox && cfg->host[0])) {
+               dlg_end(dlg, 1);       /* it's all over, and succeeded */
+           }
+       } else if (ctrl == ssd->savebutton) {
+           int isdef = !strcmp(ssd->savedsession, "Default Settings");
+           if (!ssd->savedsession[0]) {
+               int i = dlg_listbox_index(ctrl, dlg);
+               if (i < 0) {
+                   dlg_beep(dlg);
+                   return;
+               }
+               isdef = !strcmp(ssd->sesslist->sessions[i], "Default Settings");
+               if (!isdef) {
+                   strncpy(ssd->savedsession, ssd->sesslist->sessions[i],
+                           sizeof(ssd->savedsession));
+                   ssd->savedsession[sizeof(ssd->savedsession)-1] = '\0';
+               } else {
+                   ssd->savedsession[0] = '\0';
+               }
+           }
+           save_settings(ssd->savedsession, isdef, cfg);
+           get_sesslist(ssd->sesslist, FALSE);
+           get_sesslist(ssd->sesslist, TRUE);
+           dlg_refresh(ssd->editbox, dlg);
+           dlg_refresh(ssd->listbox, dlg);
+       } else if (ctrl == ssd->delbutton) {
+           int i = dlg_listbox_index(ctrl, dlg);
+           if (i <= 0) {
+               dlg_beep(dlg);
+           } else {
+               del_settings(ssd->sesslist->sessions[i]);
+               get_sesslist(ssd->sesslist, FALSE);
+               get_sesslist(ssd->sesslist, TRUE);
+               dlg_refresh(ssd->listbox, dlg);
+           }
+       } else if (ctrl == ssd->okbutton) {
+           /*
+            * Annoying special case. If the `Open' button is
+            * pressed while no host name is currently set, _and_
+            * the session list previously had the focus, _and_
+            * there was a session selected in that which had a
+            * valid host name in it, then load it and go.
+            */
+           if (dlg_last_focused(dlg) == ssd->listbox && !*cfg->host) {
+               Config cfg2;
+               if (!load_selected_session(ssd, dlg, &cfg2)) {
+                   dlg_beep(dlg);
+                   return;
+               }
+               /* If at this point we have a valid session, go! */
+               if (*cfg2.host) {
+                   *cfg = cfg2;       /* structure copy */
+                   dlg_end(dlg, 1);
+               } else
+                   dlg_beep(dlg);
+           }
+
+           /*
+            * Otherwise, do the normal thing: if we have a valid
+            * session, get going.
+            */
+           if (*cfg->host) {
+               dlg_end(dlg, 1);
+           } else
+               dlg_beep(dlg);
+       } else if (ctrl == ssd->cancelbutton) {
+           dlg_end(dlg, 0);
+       }
+    }
+}
+
+struct charclass_data {
+    union control *listbox, *editbox, *button;
+};
+
+static void charclass_handler(union control *ctrl, void *dlg,
+                             void *data, int event)
+{
+    Config *cfg = (Config *)data;
+    struct charclass_data *ccd =
+       (struct charclass_data *)ctrl->generic.context.p;
+
+    if (event == EVENT_REFRESH) {
+       if (ctrl == ccd->listbox) {
+           int i;
+           dlg_update_start(ctrl, dlg);
+           dlg_listbox_clear(ctrl, dlg);
+           for (i = 0; i < 128; i++) {
+               char str[100];
+               sprintf(str, "%d\t(0x%02X)\t%c\t%d", i, i,
+                       (i >= 0x21 && i != 0x7F) ? i : ' ', cfg->wordness[i]);
+               dlg_listbox_add(ctrl, dlg, str);
+           }
+           dlg_update_done(ctrl, dlg);
+       }
+    } else if (event == EVENT_ACTION) {
+       if (ctrl == ccd->button) {
+           char str[100];
+           int i, n;
+           dlg_editbox_get(ccd->editbox, dlg, str, sizeof(str));
+           n = atoi(str);
+           for (i = 0; i < 128; i++) {
+               if (dlg_listbox_issel(ccd->listbox, dlg, i))
+                   cfg->wordness[i] = n;
+           }
+           dlg_refresh(ccd->listbox, dlg);
+       }
+    }
+}
+
+struct colour_data {
+    union control *listbox, *rgbtext, *button;
+};
+
+static const char *const colours[] = {
+    "Default Foreground", "Default Bold Foreground",
+    "Default Background", "Default Bold Background",
+    "Cursor Text", "Cursor Colour",
+    "ANSI Black", "ANSI Black Bold",
+    "ANSI Red", "ANSI Red Bold",
+    "ANSI Green", "ANSI Green Bold",
+    "ANSI Yellow", "ANSI Yellow Bold",
+    "ANSI Blue", "ANSI Blue Bold",
+    "ANSI Magenta", "ANSI Magenta Bold",
+    "ANSI Cyan", "ANSI Cyan Bold",
+    "ANSI White", "ANSI White Bold"
+};
+
+static void colour_handler(union control *ctrl, void *dlg,
+                           void *data, int event)
+{
+    Config *cfg = (Config *)data;
+    struct colour_data *cd =
+       (struct colour_data *)ctrl->generic.context.p;
+    int update = FALSE, r, g, b;
+
+    if (event == EVENT_REFRESH) {
+       if (ctrl == cd->listbox) {
+           int i;
+           dlg_update_start(ctrl, dlg);
+           dlg_listbox_clear(ctrl, dlg);
+           for (i = 0; i < lenof(colours); i++)
+               dlg_listbox_add(ctrl, dlg, colours[i]);
+           dlg_update_done(ctrl, dlg);
+           dlg_text_set(cd->rgbtext, dlg, "");
+       }
+    } else if (event == EVENT_SELCHANGE) {
+       if (ctrl == cd->listbox) {
+           /* The user has selected a colour. Update the RGB text. */
+           int i = dlg_listbox_index(ctrl, dlg);
+           if (i < 0) {
+               dlg_beep(dlg);
+               return;
+           }
+           r = cfg->colours[i][0];
+           g = cfg->colours[i][1];
+           b = cfg->colours[i][2];
+           update = TRUE;
+       }
+    } else if (event == EVENT_ACTION) {
+       if (ctrl == cd->button) {
+           int i = dlg_listbox_index(cd->listbox, dlg);
+           if (i < 0) {
+               dlg_beep(dlg);
+               return;
+           }
+           /*
+            * Start a colour selector, which will send us an
+            * EVENT_CALLBACK when it's finished and allow us to
+            * pick up the results.
+            */
+           dlg_coloursel_start(ctrl, dlg,
+                               cfg->colours[i][0],
+                               cfg->colours[i][1],
+                               cfg->colours[i][2]);
+       }
+    } else if (event == EVENT_CALLBACK) {
+       if (ctrl == cd->button) {
+           int i = dlg_listbox_index(cd->listbox, dlg);
+           /*
+            * Collect the results of the colour selector. Will
+            * return nonzero on success, or zero if the colour
+            * selector did nothing (user hit Cancel, for example).
+            */
+           if (dlg_coloursel_results(ctrl, dlg, &r, &g, &b)) {
+               cfg->colours[i][0] = r;
+               cfg->colours[i][1] = g;
+               cfg->colours[i][2] = b;
+               update = TRUE;
+           }
+       }
+    }
+
+    if (update) {
+       char buf[40];
+       sprintf(buf, "%02x/%02x/%02x", r, g, b);
+       dlg_text_set(cd->rgbtext, dlg, buf);
+    }
+}
+
+struct environ_data {
+    union control *varbox, *valbox, *addbutton, *rembutton, *listbox;
+};
+
+static void environ_handler(union control *ctrl, void *dlg,
+                           void *data, int event)
+{
+    Config *cfg = (Config *)data;
+    struct environ_data *ed =
+       (struct environ_data *)ctrl->generic.context.p;
+
+    if (event == EVENT_REFRESH) {
+       if (ctrl == ed->listbox) {
+           char *p = cfg->environmt;
+           dlg_update_start(ctrl, dlg);
+           dlg_listbox_clear(ctrl, dlg);
+           while (*p) {
+               dlg_listbox_add(ctrl, dlg, p);
+               p += strlen(p) + 1;
+           }
+           dlg_update_done(ctrl, dlg);
+       }
+    } else if (event == EVENT_ACTION) {
+       if (ctrl == ed->addbutton) {
+           char str[sizeof(cfg->environmt)];
+           char *p;
+           dlg_editbox_get(ed->varbox, dlg, str, sizeof(str)-1);
+           if (!*str) {
+               dlg_beep(dlg);
+               return;
+           }
+           p = str + strlen(str);
+           *p++ = '\t';
+           dlg_editbox_get(ed->valbox, dlg, p, sizeof(str)-1 - (p - str));
+           if (!*p) {
+               dlg_beep(dlg);
+               return;
+           }
+           p = cfg->environmt;
+           while (*p) {
+               while (*p)
+                   p++;
+               p++;
+           }
+           if ((p - cfg->environmt) + strlen(str) + 2 <
+               sizeof(cfg->environmt)) {
+               strcpy(p, str);
+               p[strlen(str) + 1] = '\0';
+               dlg_listbox_add(ed->listbox, dlg, str);
+               dlg_editbox_set(ed->varbox, dlg, "");
+               dlg_editbox_set(ed->valbox, dlg, "");
+           } else {
+               dlg_error_msg(dlg, "Environment too big");
+           }
+       } else if (ctrl == ed->rembutton) {
+           int i = dlg_listbox_index(ed->listbox, dlg);
+           if (i < 0) {
+               dlg_beep(dlg);
+           } else {
+               char *p, *q;
+
+               dlg_listbox_del(ed->listbox, dlg, i);
+               p = cfg->environmt;
+               while (i > 0) {
+                   if (!*p)
+                       goto disaster;
+                   while (*p)
+                       p++;
+                   p++;
+                   i--;
+               }
+               q = p;
+               if (!*p)
+                   goto disaster;
+               while (*p)
+                   p++;
+               p++;
+               while (*p) {
+                   while (*p)
+                       *q++ = *p++;
+                   *q++ = *p++;
+               }
+               *q = '\0';
+               disaster:;
+           }
+       }
+    }
+}
+
+struct portfwd_data {
+    union control *addbutton, *rembutton, *listbox;
+    union control *sourcebox, *destbox, *direction;
+};
+
+static void portfwd_handler(union control *ctrl, void *dlg,
+                           void *data, int event)
+{
+    Config *cfg = (Config *)data;
+    struct portfwd_data *pfd =
+       (struct portfwd_data *)ctrl->generic.context.p;
+
+    if (event == EVENT_REFRESH) {
+       if (ctrl == pfd->listbox) {
+           char *p = cfg->portfwd;
+           dlg_update_start(ctrl, dlg);
+           dlg_listbox_clear(ctrl, dlg);
+           while (*p) {
+               dlg_listbox_add(ctrl, dlg, p);
+               p += strlen(p) + 1;
+           }
+           dlg_update_done(ctrl, dlg);
+       }
+    } else if (event == EVENT_ACTION) {
+       if (ctrl == pfd->addbutton) {
+           char str[sizeof(cfg->portfwd)];
+           char *p;
+           if (dlg_radiobutton_get(pfd->direction, dlg) == 0)
+               str[0] = 'L';
+           else
+               str[0] = 'R';
+           dlg_editbox_get(pfd->sourcebox, dlg, str+1, sizeof(str) - 2);
+           if (!str[1]) {
+               dlg_error_msg(dlg, "You need to specify a source port number");
+               return;
+           }
+           p = str + strlen(str);
+           *p++ = '\t';
+           dlg_editbox_get(pfd->destbox, dlg, p, sizeof(str)-1 - (p - str));
+           if (!*p || !strchr(p, ':')) {
+               dlg_error_msg(dlg,
+                             "You need to specify a destination address\n"
+                             "in the form \"host.name:port\"");
+               return;
+           }
+           p = cfg->portfwd;
+           while (*p) {
+               while (*p)
+                   p++;
+               p++;
+           }
+           if ((p - cfg->portfwd) + strlen(str) + 2 <
+               sizeof(cfg->portfwd)) {
+               strcpy(p, str);
+               p[strlen(str) + 1] = '\0';
+               dlg_listbox_add(pfd->listbox, dlg, str);
+               dlg_editbox_set(pfd->sourcebox, dlg, "");
+               dlg_editbox_set(pfd->destbox, dlg, "");
+           } else {
+               dlg_error_msg(dlg, "Too many forwardings");
+           }
+       } else if (ctrl == pfd->rembutton) {
+           int i = dlg_listbox_index(pfd->listbox, dlg);
+           if (i < 0)
+               dlg_beep(dlg);
+           else {
+               char *p, *q;
+
+               dlg_listbox_del(pfd->listbox, dlg, i);
+               p = cfg->portfwd;
+               while (i > 0) {
+                   if (!*p)
+                       goto disaster2;
+                   while (*p)
+                       p++;
+                   p++;
+                   i--;
+               }
+               q = p;
+               if (!*p)
+                   goto disaster2;
+               while (*p)
+                   p++;
+               p++;
+               while (*p) {
+                   while (*p)
+                       *q++ = *p++;
+                   *q++ = *p++;
+               }
+               *q = '\0';
+               disaster2:;
+           }
+       }
+    }
+}
+
+void setup_config_box(struct controlbox *b, struct sesslist *sesslist,
+                     int midsession, int protocol)
+{
+    struct controlset *s;
+    struct sessionsaver_data *ssd;
+    struct charclass_data *ccd;
+    struct colour_data *cd;
+    struct environ_data *ed;
+    struct portfwd_data *pfd;
+    union control *c;
+
+    ssd = (struct sessionsaver_data *)
+       ctrl_alloc(b, sizeof(struct sessionsaver_data));
+    ssd->sesslist = (midsession ? NULL : sesslist);
+
+    /*
+     * The standard panel that appears at the bottom of all panels:
+     * Open, Cancel, Apply etc.
+     */
+    s = ctrl_getset(b, "", "", "");
+    ctrl_columns(s, 5, 20, 20, 20, 20, 20);
+    ssd->okbutton = ctrl_pushbutton(s,
+                                   (midsession ? "Apply" : "Open"),
+                                   (char)(midsession ? 'a' : 'o'),
+                                   HELPCTX(no_help),
+                                   sessionsaver_handler, P(ssd));
+    ssd->okbutton->button.isdefault = TRUE;
+    ssd->okbutton->generic.column = 3;
+    ssd->cancelbutton = ctrl_pushbutton(s, "Cancel", 'c', HELPCTX(no_help),
+                                       sessionsaver_handler, P(ssd));
+    ssd->cancelbutton->generic.column = 4;
+    /* We carefully don't close the 5-column part, so that platform-
+     * specific add-ons can put extra buttons alongside Open and Cancel. */
+
+    /*
+     * The Session panel.
+     */
+    ctrl_settitle(b, "Session", "Basic options for your PuTTY session");
+
+    if (!midsession) {
+       s = ctrl_getset(b, "Session", "hostport",
+                       "Specify your connection by host name or IP address");
+       ctrl_columns(s, 2, 75, 25);
+       c = ctrl_editbox(s, "Host Name (or IP address)", 'n', 100,
+                        HELPCTX(session_hostname),
+                        dlg_stdeditbox_handler, I(offsetof(Config,host)),
+                        I(sizeof(((Config *)0)->host)));
+       c->generic.column = 0;
+       c = ctrl_editbox(s, "Port", 'p', 100, HELPCTX(session_hostname),
+                        dlg_stdeditbox_handler,
+                        I(offsetof(Config,port)), I(-1));
+       c->generic.column = 1;
+       ctrl_columns(s, 1, 100);
+       if (backends[3].backend == NULL) {
+           ctrl_radiobuttons(s, "Protocol:", NO_SHORTCUT, 3,
+                             HELPCTX(session_hostname),
+                             protocolbuttons_handler, P(c),
+                             "Raw", 'r', I(PROT_RAW),
+                             "Telnet", 't', I(PROT_TELNET),
+                             "Rlogin", 'i', I(PROT_RLOGIN),
+                             NULL);
+       } else {
+           ctrl_radiobuttons(s, "Protocol:", NO_SHORTCUT, 4,
+                             HELPCTX(session_hostname),
+                             protocolbuttons_handler, P(c),
+                             "Raw", 'r', I(PROT_RAW),
+                             "Telnet", 't', I(PROT_TELNET),
+                             "Rlogin", 'i', I(PROT_RLOGIN),
+                             "SSH", 's', I(PROT_SSH),
+                             NULL);
+       }
+
+       s = ctrl_getset(b, "Session", "savedsessions",
+                       "Load, save or delete a stored session");
+       ctrl_columns(s, 2, 75, 25);
+       ssd->savedsession[0] = '\0';
+       ssd->sesslist = sesslist;
+       ssd->editbox = ctrl_editbox(s, "Saved Sessions", 'e', 100,
+                                   HELPCTX(session_saved),
+                                   sessionsaver_handler, P(ssd), P(NULL));
+       ssd->editbox->generic.column = 0;
+       /* Reset columns so that the buttons are alongside the list, rather
+        * than alongside that edit box. */
+       ctrl_columns(s, 1, 100);
+       ctrl_columns(s, 2, 75, 25);
+       ssd->loadbutton = ctrl_pushbutton(s, "Load", 'l',
+                                         HELPCTX(session_saved),
+                                         sessionsaver_handler, P(ssd));
+       ssd->loadbutton->generic.column = 1;
+       ssd->savebutton = ctrl_pushbutton(s, "Save", 'v',
+                                         HELPCTX(session_saved),
+                                         sessionsaver_handler, P(ssd));
+       ssd->savebutton->generic.column = 1;
+       ssd->delbutton = ctrl_pushbutton(s, "Delete", 'd',
+                                        HELPCTX(session_saved),
+                                        sessionsaver_handler, P(ssd));
+       ssd->delbutton->generic.column = 1;
+       ssd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT,
+                                   HELPCTX(session_saved),
+                                   sessionsaver_handler, P(ssd));
+       ssd->listbox->generic.column = 0;
+       ssd->listbox->listbox.height = 7;
+       ctrl_columns(s, 1, 100);
+    }
+
+    s = ctrl_getset(b, "Session", "otheropts", NULL);
+    c = ctrl_radiobuttons(s, "Close window on exit:", 'w', 4,
+                         HELPCTX(session_coe),
+                         dlg_stdradiobutton_handler,
+                         I(offsetof(Config, close_on_exit)),
+                         "Always", I(FORCE_ON),
+                         "Never", I(FORCE_OFF),
+                         "Only on clean exit", I(AUTO), NULL);
+
+    /*
+     * The Session/Logging panel.
+     */
+    ctrl_settitle(b, "Session/Logging", "Options controlling session logging");
+
+    s = ctrl_getset(b, "Session/Logging", "main", NULL);
+    ctrl_radiobuttons(s, "Session logging:", NO_SHORTCUT, 1,
+                     HELPCTX(logging_main),
+                     dlg_stdradiobutton_handler, I(offsetof(Config, logtype)),
+                     "Logging turned off completely", 't', I(LGTYP_NONE),
+                     "Log printable output only", 'p', I(LGTYP_ASCII),
+                     "Log all session output", 'l', I(LGTYP_DEBUG),
+                     "Log SSH packet data", 's', I(LGTYP_PACKETS),
+                     NULL);
+    ctrl_filesel(s, "Log file name:", 'f',
+                NULL, TRUE, "Select session log file name",
+                HELPCTX(logging_filename),
+                dlg_stdfilesel_handler, I(offsetof(Config, logfilename)));
+    ctrl_text(s, "(Log file name can contain &Y, &M, &D for date,"
+             " &T for time, and &H for host name)",
+             HELPCTX(logging_filename));
+    ctrl_radiobuttons(s, "What to do if the log file already exists:", 'e', 1,
+                     HELPCTX(logging_exists),
+                     dlg_stdradiobutton_handler, I(offsetof(Config,logxfovr)),
+                     "Always overwrite it", I(LGXF_OVR),
+                     "Always append to the end of it", I(LGXF_APN),
+                     "Ask the user every time", I(LGXF_ASK), NULL);
+
+    /*
+     * The Terminal panel.
+     */
+    ctrl_settitle(b, "Terminal", "Options controlling the terminal emulation");
+
+    s = ctrl_getset(b, "Terminal", "general", "Set various terminal options");
+    ctrl_checkbox(s, "Auto wrap mode initially on", 'w',
+                 HELPCTX(terminal_autowrap),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,wrap_mode)));
+    ctrl_checkbox(s, "DEC Origin Mode initially on", 'd',
+                 HELPCTX(terminal_decom),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,dec_om)));
+    ctrl_checkbox(s, "Implicit CR in every LF", 'r',
+                 HELPCTX(terminal_lfhascr),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,lfhascr)));
+    ctrl_checkbox(s, "Use background colour to erase screen", 'e',
+                 HELPCTX(terminal_bce),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,bce)));
+    ctrl_checkbox(s, "Enable blinking text", 'n',
+                 HELPCTX(terminal_blink),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,blinktext)));
+    ctrl_editbox(s, "Answerback to ^E:", 's', 100,
+                HELPCTX(terminal_answerback),
+                dlg_stdeditbox_handler, I(offsetof(Config,answerback)),
+                I(sizeof(((Config *)0)->answerback)));
+
+    s = ctrl_getset(b, "Terminal", "ldisc", "Line discipline options");
+    ctrl_radiobuttons(s, "Local echo:", 'l', 3,
+                     HELPCTX(terminal_localecho),
+                     dlg_stdradiobutton_handler,I(offsetof(Config,localecho)),
+                     "Auto", I(AUTO),
+                     "Force on", I(FORCE_ON),
+                     "Force off", I(FORCE_OFF), NULL);
+    ctrl_radiobuttons(s, "Local line editing:", 't', 3,
+                     HELPCTX(terminal_localedit),
+                     dlg_stdradiobutton_handler,I(offsetof(Config,localedit)),
+                     "Auto", I(AUTO),
+                     "Force on", I(FORCE_ON),
+                     "Force off", I(FORCE_OFF), NULL);
+
+    s = ctrl_getset(b, "Terminal", "printing", "Remote-controlled printing");
+    ctrl_combobox(s, "Printer to send ANSI printer output to:", 'p', 100,
+                 HELPCTX(terminal_printing),
+                 printerbox_handler, P(NULL), P(NULL));
+
+    /*
+     * The Terminal/Keyboard panel.
+     */
+    ctrl_settitle(b, "Terminal/Keyboard",
+                 "Options controlling the effects of keys");
+
+    s = ctrl_getset(b, "Terminal/Keyboard", "mappings",
+                   "Change the sequences sent by:");
+    ctrl_radiobuttons(s, "The Backspace key", 'b', 2,
+                     HELPCTX(keyboard_backspace),
+                     dlg_stdradiobutton_handler,
+                     I(offsetof(Config, bksp_is_delete)),
+                     "Control-H", I(0), "Control-? (127)", I(1), NULL);
+    ctrl_radiobuttons(s, "The Home and End keys", 'e', 2,
+                     HELPCTX(keyboard_homeend),
+                     dlg_stdradiobutton_handler,
+                     I(offsetof(Config, rxvt_homeend)),
+                     "Standard", I(0), "rxvt", I(1), NULL);
+    ctrl_radiobuttons(s, "The Function keys and keypad", 'f', 3,
+                     HELPCTX(keyboard_funkeys),
+                     dlg_stdradiobutton_handler,
+                     I(offsetof(Config, funky_type)),
+                     "ESC[n~", I(0), "Linux", I(1), "Xterm R6", I(2),
+                     "VT400", I(3), "VT100+", I(4), "SCO", I(5), NULL);
+
+    s = ctrl_getset(b, "Terminal/Keyboard", "appkeypad",
+                   "Application keypad settings:");
+    ctrl_radiobuttons(s, "Initial state of cursor keys:", 'r', 3,
+                     HELPCTX(keyboard_appcursor),
+                     dlg_stdradiobutton_handler,
+                     I(offsetof(Config, app_cursor)),
+                     "Normal", I(0), "Application", I(1), NULL);
+    ctrl_radiobuttons(s, "Initial state of numeric keypad:", 'n', 3,
+                     HELPCTX(keyboard_appkeypad),
+                     numeric_keypad_handler, P(NULL),
+                     "Normal", I(0), "Application", I(1), "NetHack", I(2),
+                     NULL);
+
+    /*
+     * The Terminal/Bell panel.
+     */
+    ctrl_settitle(b, "Terminal/Bell",
+                 "Options controlling the terminal bell");
+
+    s = ctrl_getset(b, "Terminal/Bell", "style", "Set the style of bell");
+    ctrl_radiobuttons(s, "Action to happen when a bell occurs:", 'b', 1,
+                     HELPCTX(bell_style),
+                     dlg_stdradiobutton_handler, I(offsetof(Config, beep)),
+                     "None (bell disabled)", I(BELL_DISABLED),
+                     "Make default system alert sound", I(BELL_DEFAULT),
+                     "Visual bell (flash window)", I(BELL_VISUAL), NULL);
+
+    s = ctrl_getset(b, "Terminal/Bell", "overload",
+                   "Control the bell overload behaviour");
+    ctrl_checkbox(s, "Bell is temporarily disabled when over-used", 'd',
+                 HELPCTX(bell_overload),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,bellovl)));
+    ctrl_editbox(s, "Over-use means this many bells...", 'm', 20,
+                HELPCTX(bell_overload),
+                dlg_stdeditbox_handler, I(offsetof(Config,bellovl_n)), I(-1));
+    ctrl_editbox(s, "... in this many seconds", 't', 20,
+                HELPCTX(bell_overload),
+                dlg_stdeditbox_handler, I(offsetof(Config,bellovl_t)),
+                I(-1000));
+    ctrl_text(s, "The bell is re-enabled after a few seconds of silence.",
+             HELPCTX(bell_overload));
+    ctrl_editbox(s, "Seconds of silence required", 's', 20,
+                HELPCTX(bell_overload),
+                dlg_stdeditbox_handler, I(offsetof(Config,bellovl_s)),
+                I(-1000));
+
+    /*
+     * The Terminal/Features panel.
+     */
+    ctrl_settitle(b, "Terminal/Features",
+                 "Enabling and disabling advanced terminal features");
+
+    s = ctrl_getset(b, "Terminal/Features", "main", NULL);
+    ctrl_checkbox(s, "Disable application cursor keys mode", 'u',
+                 HELPCTX(features_application),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,no_applic_c)));
+    ctrl_checkbox(s, "Disable application keypad mode", 'k',
+                 HELPCTX(features_application),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,no_applic_k)));
+    ctrl_checkbox(s, "Disable xterm-style mouse reporting", 'x',
+                 HELPCTX(features_mouse),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,no_mouse_rep)));
+    ctrl_checkbox(s, "Disable remote-controlled terminal resizing", 's',
+                 HELPCTX(features_resize),
+                 dlg_stdcheckbox_handler,
+                 I(offsetof(Config,no_remote_resize)));
+    ctrl_checkbox(s, "Disable switching to alternate terminal screen", 'w',
+                 HELPCTX(features_altscreen),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,no_alt_screen)));
+    ctrl_checkbox(s, "Disable remote-controlled window title changing", 't',
+                 HELPCTX(features_retitle),
+                 dlg_stdcheckbox_handler,
+                 I(offsetof(Config,no_remote_wintitle)));
+    ctrl_checkbox(s, "Disable destructive backspace on server sending ^?",'b',
+                 HELPCTX(features_dbackspace),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,no_dbackspace)));
+    ctrl_checkbox(s, "Disable remote-controlled character set configuration",
+                 'r', HELPCTX(features_charset), dlg_stdcheckbox_handler,
+                 I(offsetof(Config,no_remote_charset)));
+
+    /*
+     * The Window panel.
+     */
+    ctrl_settitle(b, "Window", "Options controlling PuTTY's window");
+
+    s = ctrl_getset(b, "Window", "size", "Set the size of the window");
+    ctrl_columns(s, 2, 50, 50);
+    c = ctrl_editbox(s, "Rows", 'r', 100,
+                    HELPCTX(window_size),
+                    dlg_stdeditbox_handler, I(offsetof(Config,height)),I(-1));
+    c->generic.column = 0;
+    c = ctrl_editbox(s, "Columns", 'm', 100,
+                    HELPCTX(window_size),
+                    dlg_stdeditbox_handler, I(offsetof(Config,width)), I(-1));
+    c->generic.column = 1;
+    ctrl_columns(s, 1, 100);
+
+    s = ctrl_getset(b, "Window", "scrollback",
+                   "Control the scrollback in the window");
+    ctrl_editbox(s, "Lines of scrollback", 's', 50,
+                HELPCTX(window_scrollback),
+                dlg_stdeditbox_handler, I(offsetof(Config,savelines)), I(-1));
+    ctrl_checkbox(s, "Display scrollbar", 'd',
+                 HELPCTX(window_scrollback),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,scrollbar)));
+    ctrl_checkbox(s, "Display scrollbar in full screen mode", 'i',
+                 HELPCTX(window_scrollback),
+                 dlg_stdcheckbox_handler,
+                 I(offsetof(Config,scrollbar_in_fullscreen)));
+    ctrl_checkbox(s, "Reset scrollback on keypress", 'k',
+                 HELPCTX(window_scrollback),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,scroll_on_key)));
+    ctrl_checkbox(s, "Reset scrollback on display activity", 'p',
+                 HELPCTX(window_scrollback),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,scroll_on_disp)));
+
+    /*
+     * The Window/Appearance panel.
+     */
+    ctrl_settitle(b, "Window/Appearance",
+                 "Configure the appearance of PuTTY's window");
+
+    s = ctrl_getset(b, "Window/Appearance", "cursor",
+                   "Adjust the use of the cursor");
+    ctrl_radiobuttons(s, "Cursor appearance:", NO_SHORTCUT, 3,
+                     HELPCTX(appearance_cursor),
+                     dlg_stdradiobutton_handler,
+                     I(offsetof(Config, cursor_type)),
+                     "Block", 'l', I(0),
+                     "Underline", 'u', I(1),
+                     "Vertical line", 'v', I(2), NULL);
+    ctrl_checkbox(s, "Cursor blinks", 'b',
+                 HELPCTX(appearance_cursor),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,blink_cur)));
+
+    s = ctrl_getset(b, "Window/Appearance", "font",
+                   "Font settings");
+    ctrl_fontsel(s, "Font used in the terminal window", 'n',
+                HELPCTX(appearance_font),
+                dlg_stdfontsel_handler, I(offsetof(Config, font)));
+
+    s = ctrl_getset(b, "Window/Appearance", "mouse",
+                   "Adjust the use of the mouse pointer");
+    ctrl_checkbox(s, "Hide mouse pointer when typing in window", 'p',
+                 HELPCTX(appearance_hidemouse),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,hide_mouseptr)));
+
+    s = ctrl_getset(b, "Window/Appearance", "border",
+                   "Adjust the window border");
+    ctrl_editbox(s, "Gap between text and window edge:", NO_SHORTCUT, 20,
+                HELPCTX(appearance_border),
+                dlg_stdeditbox_handler,
+                I(offsetof(Config,window_border)), I(-1));
+
+    /*
+     * The Window/Behaviour panel.
+     */
+    ctrl_settitle(b, "Window/Behaviour",
+                 "Configure the behaviour of PuTTY's window");
+
+    s = ctrl_getset(b, "Window/Behaviour", "title",
+                   "Adjust the behaviour of the window title");
+    ctrl_editbox(s, "Window title:", 't', 100,
+                HELPCTX(appearance_title),
+                dlg_stdeditbox_handler, I(offsetof(Config,wintitle)),
+                I(sizeof(((Config *)0)->wintitle)));
+    ctrl_checkbox(s, "Separate window and icon titles", 'i',
+                 HELPCTX(appearance_title),
+                 dlg_stdcheckbox_handler,
+                 I(CHECKBOX_INVERT | offsetof(Config,win_name_always)));
+
+    s = ctrl_getset(b, "Window/Behaviour", "main", NULL);
+    ctrl_checkbox(s, "Warn before closing window", 'w',
+                 HELPCTX(behaviour_closewarn),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,warn_on_close)));
+
+    /*
+     * The Window/Translation panel.
+     */
+    ctrl_settitle(b, "Window/Translation",
+                 "Options controlling character set translation");
+
+    s = ctrl_getset(b, "Window/Translation", "trans",
+                   "Character set translation on received data");
+    ctrl_combobox(s, "Received data assumed to be in which character set:",
+                 'r', 100, HELPCTX(translation_codepage),
+                 codepage_handler, P(NULL), P(NULL));
+
+    s = ctrl_getset(b, "Window/Translation", "linedraw",
+                   "Adjust how PuTTY displays line drawing characters");
+    ctrl_radiobuttons(s, "Handling of line drawing characters:", NO_SHORTCUT,1,
+                     HELPCTX(translation_linedraw),
+                     dlg_stdradiobutton_handler,
+                     I(offsetof(Config, vtmode)),
+                     "Font has XWindows encoding", 'x', I(VT_XWINDOWS),
+                     "Poor man's line drawing (+, - and |)",'p',I(VT_POORMAN),
+                     "Unicode mode", 'u', I(VT_UNICODE), NULL);
+
+    /*
+     * The Window/Selection panel.
+     */
+    ctrl_settitle(b, "Window/Selection", "Options controlling copy and paste");
+
+    s = ctrl_getset(b, "Window/Selection", "trans",
+                   "Translation of pasted characters");
+    ctrl_checkbox(s, "Don't translate line drawing chars into +, - and |",'d',
+                 HELPCTX(selection_linedraw),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,rawcnp)));
+       
+    s = ctrl_getset(b, "Window/Selection", "mouse",
+                   "Control use of mouse");
+    ctrl_checkbox(s, "Shift overrides application's use of mouse", 'p',
+                 HELPCTX(selection_shiftdrag),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,mouse_override)));
+    ctrl_radiobuttons(s,
+                     "Default selection mode (Alt+drag does the other one):",
+                     NO_SHORTCUT, 2,
+                     HELPCTX(selection_rect),
+                     dlg_stdradiobutton_handler,
+                     I(offsetof(Config, rect_select)),
+                     "Normal", 'n', I(0),
+                     "Rectangular block", 'r', I(1), NULL);
+
+    s = ctrl_getset(b, "Window/Selection", "charclass",
+                   "Control the select-one-word-at-a-time mode");
+    ccd = (struct charclass_data *)
+       ctrl_alloc(b, sizeof(struct charclass_data));
+    ccd->listbox = ctrl_listbox(s, "Character classes:", 'e',
+                               HELPCTX(selection_charclasses),
+                               charclass_handler, P(ccd));
+    ccd->listbox->listbox.multisel = 1;
+    ccd->listbox->listbox.ncols = 4;
+    ccd->listbox->listbox.percentages = smalloc(4*sizeof(int));
+    ccd->listbox->listbox.percentages[0] = 15;
+    ccd->listbox->listbox.percentages[1] = 25;
+    ccd->listbox->listbox.percentages[2] = 20;
+    ccd->listbox->listbox.percentages[3] = 40;
+    ctrl_columns(s, 2, 67, 33);
+    ccd->editbox = ctrl_editbox(s, "Set to class", 't', 50,
+                               HELPCTX(selection_charclasses),
+                               charclass_handler, P(ccd), P(NULL));
+    ccd->editbox->generic.column = 0;
+    ccd->button = ctrl_pushbutton(s, "Set", 's',
+                                 HELPCTX(selection_charclasses),
+                                 charclass_handler, P(ccd));
+    ccd->button->generic.column = 1;
+    ctrl_columns(s, 1, 100);
+
+    /*
+     * The Window/Colours panel.
+     */
+    ctrl_settitle(b, "Window/Colours", "Options controlling use of colours");
+
+    s = ctrl_getset(b, "Window/Colours", "general",
+                   "General options for colour usage");
+    ctrl_checkbox(s, "Bolded text is a different colour", 'b',
+                 HELPCTX(colours_bold),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,bold_colour)));
+
+    s = ctrl_getset(b, "Window/Colours", "adjust",
+                   "Adjust the precise colours PuTTY displays");
+    ctrl_text(s, "Select a colour from the list, and then click the"
+             " Modify button to change its appearance.",
+             HELPCTX(colours_config));
+    ctrl_columns(s, 2, 67, 33);
+    cd = (struct colour_data *)ctrl_alloc(b, sizeof(struct colour_data));
+    cd->listbox = ctrl_listbox(s, "Select a colour to adjust:", 'u',
+                              HELPCTX(colours_config), colour_handler, P(cd));
+    cd->listbox->generic.column = 0;
+    c = ctrl_text(s, "RGB value:", HELPCTX(colours_config));
+    c->generic.column = 1;
+    cd->rgbtext = ctrl_text(s, "00/00/00", HELPCTX(colours_config));
+    cd->rgbtext->generic.column = 1;
+    cd->button = ctrl_pushbutton(s, "Modify", 'm', HELPCTX(colours_config),
+                                colour_handler, P(cd));
+    cd->button->generic.column = 1;
+    ctrl_columns(s, 1, 100);
+
+    /*
+     * The Connection panel.
+     */
+    ctrl_settitle(b, "Connection", "Options controlling the connection");
+
+    if (!midsession) {
+       s = ctrl_getset(b, "Connection", "data", "Data to send to the server");
+       ctrl_editbox(s, "Terminal-type string", 't', 50,
+                    HELPCTX(connection_termtype),
+                    dlg_stdeditbox_handler, I(offsetof(Config,termtype)),
+                    I(sizeof(((Config *)0)->termtype)));
+       ctrl_editbox(s, "Auto-login username", 'u', 50,
+                    HELPCTX(connection_username),
+                    dlg_stdeditbox_handler, I(offsetof(Config,username)),
+                    I(sizeof(((Config *)0)->username)));
+    }
+
+    s = ctrl_getset(b, "Connection", "keepalive",
+                   "Sending of null packets to keep session active");
+    ctrl_editbox(s, "Seconds between keepalives (0 to turn off)", 'k', 20,
+                HELPCTX(connection_keepalive),
+                dlg_stdeditbox_handler, I(offsetof(Config,ping_interval)),
+                I(-1));
+
+    if (!midsession) {
+       s = ctrl_getset(b, "Connection", "tcp",
+                       "Low-level TCP connection options");
+       ctrl_checkbox(s, "Disable Nagle's algorithm (TCP_NODELAY option)", 'n',
+                     HELPCTX(connection_nodelay),
+                     dlg_stdcheckbox_handler,
+                     I(offsetof(Config,tcp_nodelay)));
+    }
+
+    if (!midsession) {
+       /*
+        * The Connection/Proxy panel.
+        */
+       ctrl_settitle(b, "Connection/Proxy",
+                     "Options controlling proxy usage");
+
+       s = ctrl_getset(b, "Connection/Proxy", "basics", "Proxy basics");
+       ctrl_radiobuttons(s, "Proxy type:", NO_SHORTCUT, 4,
+                         HELPCTX(proxy_type),
+                         dlg_stdradiobutton_handler,
+                         I(offsetof(Config, proxy_type)),
+                         "None", 'n', I(PROXY_NONE),
+                         "HTTP", 't', I(PROXY_HTTP),
+                         "SOCKS", 's', I(PROXY_SOCKS),
+                         "Telnet", 'l', I(PROXY_TELNET),
+                         NULL);
+       ctrl_columns(s, 2, 80, 20);
+       c = ctrl_editbox(s, "Proxy hostname", 'y', 100,
+                        HELPCTX(proxy_main),
+                        dlg_stdeditbox_handler,
+                        I(offsetof(Config,proxy_host)),
+                        I(sizeof(((Config *)0)->proxy_host)));
+       c->generic.column = 0;
+       c = ctrl_editbox(s, "Port", 'p', 100,
+                        HELPCTX(proxy_main),
+                        dlg_stdeditbox_handler,
+                        I(offsetof(Config,proxy_port)),
+                        I(-1));
+       c->generic.column = 1;
+       ctrl_columns(s, 1, 100);
+       ctrl_editbox(s, "Exclude Hosts/IPs", 'e', 100,
+                    HELPCTX(proxy_exclude),
+                    dlg_stdeditbox_handler,
+                    I(offsetof(Config,proxy_exclude_list)),
+                    I(sizeof(((Config *)0)->proxy_exclude_list)));
+       ctrl_checkbox(s, "Consider proxying local host connections", 'x',
+                     HELPCTX(proxy_exclude),
+                     dlg_stdcheckbox_handler,
+                     I(offsetof(Config,even_proxy_localhost)));
+       ctrl_radiobuttons(s, "Do DNS name lookup at proxy end:", 'd', 3,
+                         HELPCTX(proxy_dns),
+                         dlg_stdradiobutton_handler,
+                         I(offsetof(Config, proxy_dns)),
+                         "No", I(FORCE_OFF),
+                         "Auto", I(AUTO),
+                         "Yes", I(FORCE_ON), NULL);
+       ctrl_editbox(s, "Username", 'u', 60,
+                    HELPCTX(proxy_auth),
+                    dlg_stdeditbox_handler,
+                    I(offsetof(Config,proxy_username)),
+                    I(sizeof(((Config *)0)->proxy_username)));
+       c = ctrl_editbox(s, "Password", 'w', 60,
+                        HELPCTX(proxy_auth),
+                        dlg_stdeditbox_handler,
+                        I(offsetof(Config,proxy_password)),
+                        I(sizeof(((Config *)0)->proxy_password)));
+       c->editbox.password = 1;
+
+       s = ctrl_getset(b, "Connection/Proxy", "misc",
+                       "Miscellaneous proxy settings");
+       ctrl_editbox(s, "Telnet command", 'm', 100,
+                    HELPCTX(proxy_command),
+                    dlg_stdeditbox_handler,
+                    I(offsetof(Config,proxy_telnet_command)),
+                    I(sizeof(((Config *)0)->proxy_telnet_command)));
+       ctrl_radiobuttons(s, "SOCKS Version", 'v', 2,
+                         HELPCTX(proxy_socksver),
+                         dlg_stdradiobutton_handler,
+                         I(offsetof(Config, proxy_socks_version)),
+                         "Version 5", I(5), "Version 4", I(4), NULL);
+    }
+
+    /*
+     * The Telnet panel exists in the base config box, and in a
+     * mid-session reconfig box _if_ we're using Telnet.
+     */
+    if (!midsession || protocol == PROT_TELNET) {
+       /*
+        * The Connection/Telnet panel.
+        */
+       ctrl_settitle(b, "Connection/Telnet",
+                     "Options controlling Telnet connections");
+
+       if (!midsession) {
+           s = ctrl_getset(b, "Connection/Telnet", "data",
+                           "Data to send to the server");
+           ctrl_editbox(s, "Terminal-speed string", 's', 50,
+                        HELPCTX(telnet_termspeed),
+                        dlg_stdeditbox_handler, I(offsetof(Config,termspeed)),
+                        I(sizeof(((Config *)0)->termspeed)));
+           ctrl_text(s, "Environment variables:", HELPCTX(telnet_environ));
+           ctrl_columns(s, 2, 80, 20);
+           ed = (struct environ_data *)
+               ctrl_alloc(b, sizeof(struct environ_data));
+           ed->varbox = ctrl_editbox(s, "Variable", 'v', 60,
+                                     HELPCTX(telnet_environ),
+                                     environ_handler, P(ed), P(NULL));
+           ed->varbox->generic.column = 0;
+           ed->valbox = ctrl_editbox(s, "Value", 'l', 60,
+                                     HELPCTX(telnet_environ),
+                                     environ_handler, P(ed), P(NULL));
+           ed->valbox->generic.column = 0;
+           ed->addbutton = ctrl_pushbutton(s, "Add", 'd',
+                                           HELPCTX(telnet_environ),
+                                           environ_handler, P(ed));
+           ed->addbutton->generic.column = 1;
+           ed->rembutton = ctrl_pushbutton(s, "Remove", 'r',
+                                           HELPCTX(telnet_environ),
+                                           environ_handler, P(ed));
+           ed->rembutton->generic.column = 1;
+           ctrl_columns(s, 1, 100);
+           ed->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT,
+                                      HELPCTX(telnet_environ),
+                                      environ_handler, P(ed));
+           ed->listbox->listbox.height = 3;
+       }
+
+       s = ctrl_getset(b, "Connection/Telnet", "protocol",
+                       "Telnet protocol adjustments");
+
+       if (!midsession) {
+           ctrl_radiobuttons(s, "Handling of OLD_ENVIRON ambiguity:",
+                             NO_SHORTCUT, 2,
+                             HELPCTX(telnet_oldenviron),
+                             dlg_stdradiobutton_handler,
+                             I(offsetof(Config, rfc_environ)),
+                             "BSD (commonplace)", 'b', I(0),
+                             "RFC 1408 (unusual)", 'f', I(1), NULL);
+           ctrl_radiobuttons(s, "Telnet negotiation mode:", 't', 2,
+                             HELPCTX(telnet_passive),
+                             dlg_stdradiobutton_handler,
+                             I(offsetof(Config, passive_telnet)),
+                             "Passive", I(1), "Active", I(0), NULL);
+       }
+       ctrl_checkbox(s, "Keyboard sends telnet Backspace and Interrupt", 'k',
+                     HELPCTX(telnet_specialkeys),
+                     dlg_stdcheckbox_handler,
+                     I(offsetof(Config,telnet_keyboard)));
+       ctrl_checkbox(s, "Return key sends telnet New Line instead of ^M",
+                     NO_SHORTCUT, HELPCTX(telnet_newline),
+                     dlg_stdcheckbox_handler,
+                     I(offsetof(Config,telnet_newline)));
+    }
+
+    if (!midsession) {
+
+       /*
+        * The Connection/Rlogin panel.
+        */
+       ctrl_settitle(b, "Connection/Rlogin",
+                     "Options controlling Rlogin connections");
+
+       s = ctrl_getset(b, "Connection/Rlogin", "data",
+                       "Data to send to the server");
+       ctrl_editbox(s, "Terminal-speed string", 's', 50,
+                    HELPCTX(rlogin_termspeed),
+                    dlg_stdeditbox_handler, I(offsetof(Config,termspeed)),
+                    I(sizeof(((Config *)0)->termspeed)));
+       ctrl_editbox(s, "Local username:", 'l', 50,
+                    HELPCTX(rlogin_localuser),
+                    dlg_stdeditbox_handler, I(offsetof(Config,localusername)),
+                    I(sizeof(((Config *)0)->localusername)));
+
+    }
+
+    /*
+     * All the SSH stuff is omitted in PuTTYtel.
+     */
+
+    if (!midsession && backends[3].backend != NULL) {
+
+       /*
+        * The Connection/SSH panel.
+        */
+       ctrl_settitle(b, "Connection/SSH",
+                     "Options controlling SSH connections");
+
+       s = ctrl_getset(b, "Connection/SSH", "data",
+                       "Data to send to the server");
+       ctrl_editbox(s, "Remote command:", 'r', 100,
+                    HELPCTX(ssh_command),
+                    dlg_stdeditbox_handler, I(offsetof(Config,remote_cmd)),
+                    I(sizeof(((Config *)0)->remote_cmd)));
+
+       s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options");
+       ctrl_checkbox(s, "Don't allocate a pseudo-terminal", 'p',
+                     HELPCTX(ssh_nopty),
+                     dlg_stdcheckbox_handler,
+                     I(offsetof(Config,nopty)));
+       ctrl_checkbox(s, "Enable compression", 'e',
+                     HELPCTX(ssh_compress),
+                     dlg_stdcheckbox_handler,
+                     I(offsetof(Config,compression)));
+       ctrl_radiobuttons(s, "Preferred SSH protocol version:", NO_SHORTCUT, 4,
+                         HELPCTX(ssh_protocol),
+                         dlg_stdradiobutton_handler,
+                         I(offsetof(Config, sshprot)),
+                         "1 only", 'l', I(0),
+                         "1", '1', I(1),
+                         "2", '2', I(2),
+                         "2 only", 'n', I(3), NULL);
+
+       s = ctrl_getset(b, "Connection/SSH", "encryption", "Encryption options");
+       ctrl_draglist(s, "Encryption cipher selection policy:", 's',
+                     HELPCTX(ssh_ciphers),
+                     cipherlist_handler, P(NULL));
+       ctrl_checkbox(s, "Enable non-standard use of single-DES in SSH 2", 'i',
+                     HELPCTX(ssh_ciphers),
+                     dlg_stdcheckbox_handler,
+                     I(offsetof(Config,ssh2_des_cbc)));
+
+       /*
+        * The Connection/SSH/Auth panel.
+        */
+       ctrl_settitle(b, "Connection/SSH/Auth",
+                     "Options controlling SSH authentication");
+
+       s = ctrl_getset(b, "Connection/SSH/Auth", "methods",
+                       "Authentication methods");
+       ctrl_checkbox(s, "Attempt TIS or CryptoCard auth (SSH1)", 'm',
+                     HELPCTX(ssh_auth_tis),
+                     dlg_stdcheckbox_handler,
+                     I(offsetof(Config,try_tis_auth)));
+       ctrl_checkbox(s, "Attempt \"keyboard-interactive\" auth (SSH2)",
+                     'i', HELPCTX(ssh_auth_ki),
+                     dlg_stdcheckbox_handler,
+                     I(offsetof(Config,try_ki_auth)));
+
+       s = ctrl_getset(b, "Connection/SSH/Auth", "params",
+                       "Authentication parameters");
+       ctrl_checkbox(s, "Allow agent forwarding", 'f',
+                     HELPCTX(ssh_auth_agentfwd),
+                     dlg_stdcheckbox_handler, I(offsetof(Config,agentfwd)));
+       ctrl_checkbox(s, "Allow attempted changes of username in SSH2", 'u',
+                     HELPCTX(ssh_auth_changeuser),
+                     dlg_stdcheckbox_handler,
+                     I(offsetof(Config,change_username)));
+       ctrl_filesel(s, "Private key file for authentication:", 'k',
+                    FILTER_KEY_FILES, FALSE, "Select private key file",
+                    HELPCTX(ssh_auth_privkey),
+                    dlg_stdfilesel_handler, I(offsetof(Config, keyfile)));
+
+       /*
+        * The Connection/SSH/Tunnels panel.
+        */
+       ctrl_settitle(b, "Connection/SSH/Tunnels",
+                     "Options controlling SSH tunnelling");
+
+       s = ctrl_getset(b, "Connection/SSH/Tunnels", "x11", "X11 forwarding");
+       ctrl_checkbox(s, "Enable X11 forwarding", 'e',
+                     HELPCTX(ssh_tunnels_x11),
+                     dlg_stdcheckbox_handler,I(offsetof(Config,x11_forward)));
+       ctrl_editbox(s, "X display location", 'x', 50,
+                    HELPCTX(ssh_tunnels_x11),
+                    dlg_stdeditbox_handler, I(offsetof(Config,x11_display)),
+                    I(sizeof(((Config *)0)->x11_display)));
+       ctrl_radiobuttons(s, "Remote X11 authentication protocol", 'u', 2,
+                         HELPCTX(ssh_tunnels_x11auth),
+                         dlg_stdradiobutton_handler,
+                         I(offsetof(Config, x11_auth)),
+                         "MIT-Magic-Cookie-1", I(X11_MIT),
+                         "XDM-Authorization-1", I(X11_XDM), NULL);
+
+       s = ctrl_getset(b, "Connection/SSH/Tunnels", "portfwd",
+                       "Port forwarding");
+       ctrl_checkbox(s, "Local ports accept connections from other hosts",'t',
+                     HELPCTX(ssh_tunnels_portfwd_localhost),
+                     dlg_stdcheckbox_handler,
+                     I(offsetof(Config,lport_acceptall)));
+       ctrl_checkbox(s, "Remote ports do the same (SSH v2 only)", 'p',
+                     HELPCTX(ssh_tunnels_portfwd_localhost),
+                     dlg_stdcheckbox_handler,
+                     I(offsetof(Config,rport_acceptall)));
+
+       ctrl_columns(s, 3, 55, 20, 25);
+       c = ctrl_text(s, "Forwarded ports:", HELPCTX(ssh_tunnels_portfwd));
+       c->generic.column = COLUMN_FIELD(0,2);
+       /* You want to select from the list, _then_ hit Remove. So tab order
+        * should be that way round. */
+       pfd = (struct portfwd_data *)ctrl_alloc(b,sizeof(struct portfwd_data));
+       pfd->rembutton = ctrl_pushbutton(s, "Remove", 'r',
+                                        HELPCTX(ssh_tunnels_portfwd),
+                                        portfwd_handler, P(pfd));
+       pfd->rembutton->generic.column = 2;
+       pfd->rembutton->generic.tabdelay = 1;
+       pfd->listbox = ctrl_listbox(s, NULL, NO_SHORTCUT,
+                                   HELPCTX(ssh_tunnels_portfwd),
+                                   portfwd_handler, P(pfd));
+       pfd->listbox->listbox.height = 3;
+       ctrl_tabdelay(s, pfd->rembutton);
+       ctrl_text(s, "Add new forwarded port:", HELPCTX(ssh_tunnels_portfwd));
+       /* You want to enter source, destination and type, _then_ hit Add.
+        * Again, we adjust the tab order to reflect this. */
+       pfd->addbutton = ctrl_pushbutton(s, "Add", 'd',
+                                        HELPCTX(ssh_tunnels_portfwd),
+                                        portfwd_handler, P(pfd));
+       pfd->addbutton->generic.column = 2;
+       pfd->addbutton->generic.tabdelay = 1;
+       pfd->sourcebox = ctrl_editbox(s, "Source port", 's', 40,
+                                     HELPCTX(ssh_tunnels_portfwd),
+                                     portfwd_handler, P(pfd), P(NULL));
+       pfd->sourcebox->generic.column = 0;
+       pfd->destbox = ctrl_editbox(s, "Destination", 'i', 67,
+                                   HELPCTX(ssh_tunnels_portfwd),
+                                   portfwd_handler, P(pfd), P(NULL));
+       pfd->direction = ctrl_radiobuttons(s, NULL, NO_SHORTCUT, 2,
+                                          HELPCTX(ssh_tunnels_portfwd),
+                                          portfwd_handler, P(pfd),
+                                          "Local", 'l', P(NULL),
+                                          "Remote", 'm', P(NULL), NULL);
+       ctrl_tabdelay(s, pfd->addbutton);
+       ctrl_columns(s, 1, 100);
+
+       /*
+        * The Connection/SSH/Bugs panel.
+        */
+       ctrl_settitle(b, "Connection/SSH/Bugs",
+                     "Workarounds for SSH server bugs");
+
+       s = ctrl_getset(b, "Connection/SSH/Bugs", "main",
+                       "Detection of known bugs in SSH servers");
+       ctrl_droplist(s, "Chokes on SSH1 ignore messages", 'i', 20,
+                     HELPCTX(ssh_bugs_ignore1),
+                     sshbug_handler, I(offsetof(Config,sshbug_ignore1)));
+       ctrl_droplist(s, "Refuses all SSH1 password camouflage", 's', 20,
+                     HELPCTX(ssh_bugs_plainpw1),
+                     sshbug_handler, I(offsetof(Config,sshbug_plainpw1)));
+       ctrl_droplist(s, "Chokes on SSH1 RSA authentication", 'r', 20,
+                     HELPCTX(ssh_bugs_rsa1),
+                     sshbug_handler, I(offsetof(Config,sshbug_rsa1)));
+       ctrl_droplist(s, "Miscomputes SSH2 HMAC keys", 'm', 20,
+                     HELPCTX(ssh_bugs_hmac2),
+                     sshbug_handler, I(offsetof(Config,sshbug_hmac2)));
+       ctrl_droplist(s, "Miscomputes SSH2 encryption keys", 'e', 20,
+                     HELPCTX(ssh_bugs_derivekey2),
+                     sshbug_handler, I(offsetof(Config,sshbug_derivekey2)));
+       ctrl_droplist(s, "Requires padding on SSH2 RSA signatures", 'p', 20,
+                     HELPCTX(ssh_bugs_rsapad2),
+                     sshbug_handler, I(offsetof(Config,sshbug_rsapad2)));
+       ctrl_droplist(s, "Chokes on Diffie-Hellman group exchange", 'd', 20,
+                     HELPCTX(ssh_bugs_dhgex2),
+                     sshbug_handler, I(offsetof(Config,sshbug_dhgex2)));
+       ctrl_droplist(s, "Misuses the session ID in PK auth", 'n', 20,
+                     HELPCTX(ssh_bugs_pksessid2),
+                     sshbug_handler, I(offsetof(Config,sshbug_pksessid2)));
+    }
+}
diff --git a/dialog.c b/dialog.c
new file mode 100644 (file)
index 0000000..d9d6aaf
--- /dev/null
+++ b/dialog.c
@@ -0,0 +1,587 @@
+/*
+ * dialog.c - a reasonably platform-independent mechanism for
+ * describing dialog boxes.
+ */
+
+#include <assert.h>
+#include <limits.h>
+#include <stdarg.h>
+
+#define DEFINE_INTORPTR_FNS
+
+#include "putty.h"
+#include "dialog.h"
+
+int ctrl_path_elements(char *path)
+{
+    int i = 1;
+    while (*path) {
+       if (*path == '/') i++;
+       path++;
+    }
+    return i;
+}
+
+/* Return the number of matching path elements at the starts of p1 and p2,
+ * or INT_MAX if the paths are identical. */
+int ctrl_path_compare(char *p1, char *p2)
+{
+    int i = 0;
+    while (*p1 || *p2) {
+       if ((*p1 == '/' || *p1 == '\0') &&
+           (*p2 == '/' || *p2 == '\0'))
+           i++;                       /* a whole element matches, ooh */
+       if (*p1 != *p2)
+           return i;                  /* mismatch */
+       p1++, p2++;
+    }
+    return INT_MAX;                   /* exact match */
+}
+
+struct controlbox *ctrl_new_box(void)
+{
+    struct controlbox *ret = smalloc(sizeof(struct controlbox));
+
+    ret->nctrlsets = ret->ctrlsetsize = 0;
+    ret->ctrlsets = NULL;
+    ret->nfrees = ret->freesize = 0;
+    ret->frees = NULL;
+
+    return ret;
+}
+
+void ctrl_free_box(struct controlbox *b)
+{
+    int i;
+
+    for (i = 0; i < b->nctrlsets; i++) {
+       ctrl_free_set(b->ctrlsets[i]);
+    }
+    for (i = 0; i < b->nfrees; i++)
+       sfree(b->frees[i]);
+    sfree(b->ctrlsets);
+    sfree(b->frees);
+    sfree(b);
+}
+
+void ctrl_free_set(struct controlset *s)
+{
+    int i;
+
+    sfree(s->pathname);
+    sfree(s->boxname);
+    sfree(s->boxtitle);
+    for (i = 0; i < s->ncontrols; i++) {
+       ctrl_free(s->ctrls[i]);
+    }
+    sfree(s->ctrls);
+    sfree(s);
+}
+
+/*
+ * Find the index of first controlset in a controlbox for a given
+ * path. If that path doesn't exist, return the index where it
+ * should be inserted.
+ */
+static int ctrl_find_set(struct controlbox *b, char *path, int start)
+{
+    int i, last, thisone;
+
+    last = 0;
+    for (i = 0; i < b->nctrlsets; i++) {
+       thisone = ctrl_path_compare(path, b->ctrlsets[i]->pathname);
+       /*
+        * If `start' is true and there exists a controlset with
+        * exactly the path we've been given, we should return the
+        * index of the first such controlset we find. Otherwise,
+        * we should return the index of the first entry in which
+        * _fewer_ path elements match than they did last time.
+        */
+       if ((start && thisone == INT_MAX) || thisone < last)
+           return i;
+       last = thisone;
+    }
+    return b->nctrlsets;              /* insert at end */
+}
+
+/*
+ * Find the index of next controlset in a controlbox for a given
+ * path, or -1 if no such controlset exists. If -1 is passed as
+ * input, finds the first.
+ */
+int ctrl_find_path(struct controlbox *b, char *path, int index)
+{
+    if (index < 0)
+       index = ctrl_find_set(b, path, 1);
+    else
+       index++;
+
+    if (index < b->nctrlsets && !strcmp(path, b->ctrlsets[index]->pathname))
+       return index;
+    else
+       return -1;
+}
+
+/* Set up a panel title. */
+struct controlset *ctrl_settitle(struct controlbox *b,
+                                char *path, char *title)
+{
+    
+    struct controlset *s = smalloc(sizeof(struct controlset));
+    int index = ctrl_find_set(b, path, 1);
+    s->pathname = dupstr(path);
+    s->boxname = NULL;
+    s->boxtitle = dupstr(title);
+    s->ncontrols = s->ctrlsize = 0;
+    s->ncolumns = 0;                  /* this is a title! */
+    s->ctrls = NULL;
+    if (b->nctrlsets >= b->ctrlsetsize) {
+       b->ctrlsetsize = b->nctrlsets + 32;
+       b->ctrlsets = srealloc(b->ctrlsets,
+                              b->ctrlsetsize*sizeof(*b->ctrlsets));
+    }
+    if (index < b->nctrlsets)
+       memmove(&b->ctrlsets[index+1], &b->ctrlsets[index],
+               (b->nctrlsets-index) * sizeof(*b->ctrlsets));
+    b->ctrlsets[index] = s;
+    b->nctrlsets++;
+    return s;
+}
+
+/* Retrieve a pointer to a controlset, creating it if absent. */
+struct controlset *ctrl_getset(struct controlbox *b,
+                              char *path, char *name, char *boxtitle)
+{
+    struct controlset *s;
+    int index = ctrl_find_set(b, path, 1);
+    while (index < b->nctrlsets &&
+          !strcmp(b->ctrlsets[index]->pathname, path)) {
+       if (b->ctrlsets[index]->boxname &&
+           !strcmp(b->ctrlsets[index]->boxname, name))
+           return b->ctrlsets[index];
+       index++;
+    }
+    s = smalloc(sizeof(struct controlset));
+    s->pathname = dupstr(path);
+    s->boxname = dupstr(name);
+    s->boxtitle = boxtitle ? dupstr(boxtitle) : NULL;
+    s->ncolumns = 1;
+    s->ncontrols = s->ctrlsize = 0;
+    s->ctrls = NULL;
+    if (b->nctrlsets >= b->ctrlsetsize) {
+       b->ctrlsetsize = b->nctrlsets + 32;
+       b->ctrlsets = srealloc(b->ctrlsets,
+                              b->ctrlsetsize*sizeof(*b->ctrlsets));
+    }
+    if (index < b->nctrlsets)
+       memmove(&b->ctrlsets[index+1], &b->ctrlsets[index],
+               (b->nctrlsets-index) * sizeof(*b->ctrlsets));
+    b->ctrlsets[index] = s;
+    b->nctrlsets++;
+    return s;
+}
+
+/* Allocate some private data in a controlbox. */
+void *ctrl_alloc(struct controlbox *b, size_t size)
+{
+    void *p;
+    p = smalloc(size);
+    if (b->nfrees >= b->freesize) {
+       b->freesize = b->nfrees + 32;
+       b->frees = srealloc(b->frees, b->freesize*sizeof(*b->frees));
+    }
+    b->frees[b->nfrees++] = p;
+    return p;
+}
+
+static union control *ctrl_new(struct controlset *s, int type,
+                              intorptr helpctx, handler_fn handler,
+                              intorptr context)
+{
+    union control *c = smalloc(sizeof(union control));
+    if (s->ncontrols >= s->ctrlsize) {
+       s->ctrlsize = s->ncontrols + 32;
+       s->ctrls = srealloc(s->ctrls, s->ctrlsize * sizeof(*s->ctrls));
+    }
+    s->ctrls[s->ncontrols++] = c;
+    /*
+     * Fill in the standard fields.
+     */
+    c->generic.type = type;
+    c->generic.tabdelay = 0;
+    c->generic.column = COLUMN_FIELD(0, s->ncolumns);
+    c->generic.helpctx = helpctx;
+    c->generic.handler = handler;
+    c->generic.context = context;
+    c->generic.label = NULL;
+    return c;
+}
+
+/* `ncolumns' is followed by that many percentages, as integers. */
+union control *ctrl_columns(struct controlset *s, int ncolumns, ...)
+{
+    union control *c = ctrl_new(s, CTRL_COLUMNS, P(NULL), NULL, P(NULL));
+    assert(s->ncolumns == 1 || ncolumns == 1);
+    c->columns.ncols = ncolumns;
+    s->ncolumns = ncolumns;
+    if (ncolumns == 1) {
+       c->columns.percentages = NULL;
+    } else {
+       va_list ap;
+       int i;
+       c->columns.percentages = smalloc(ncolumns * sizeof(int));
+       va_start(ap, ncolumns);
+       for (i = 0; i < ncolumns; i++)
+           c->columns.percentages[i] = va_arg(ap, int);
+       va_end(ap);
+    }
+    return c;
+}
+
+union control *ctrl_editbox(struct controlset *s, char *label, char shortcut,
+                           int percentage,
+                           intorptr helpctx, handler_fn handler,
+                           intorptr context, intorptr context2)
+{
+    union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context);
+    c->editbox.label = label ? dupstr(label) : NULL;
+    c->editbox.shortcut = shortcut;
+    c->editbox.percentwidth = percentage;
+    c->editbox.password = 0;
+    c->editbox.has_list = 0;
+    c->editbox.context2 = context2;
+    return c;
+}
+
+union control *ctrl_combobox(struct controlset *s, char *label, char shortcut,
+                            int percentage,
+                            intorptr helpctx, handler_fn handler,
+                            intorptr context, intorptr context2)
+{
+    union control *c = ctrl_new(s, CTRL_EDITBOX, helpctx, handler, context);
+    c->editbox.label = label ? dupstr(label) : NULL;
+    c->editbox.shortcut = shortcut;
+    c->editbox.percentwidth = percentage;
+    c->editbox.password = 0;
+    c->editbox.has_list = 1;
+    c->editbox.context2 = context2;
+    return c;
+}
+
+/*
+ * `ncolumns' is followed by (alternately) radio button titles and
+ * intorptrs, until a NULL in place of a title string is seen. Each
+ * title is expected to be followed by a shortcut _iff_ `shortcut'
+ * is NO_SHORTCUT.
+ */
+union control *ctrl_radiobuttons(struct controlset *s, char *label,
+                                char shortcut, int ncolumns, intorptr helpctx,
+                                handler_fn handler, intorptr context, ...)
+{
+    va_list ap;
+    int i;
+    union control *c = ctrl_new(s, CTRL_RADIO, helpctx, handler, context);
+    c->radio.label = label ? dupstr(label) : NULL;
+    c->radio.shortcut = shortcut;
+    c->radio.ncolumns = ncolumns;
+    /*
+     * Initial pass along variable argument list to count the
+     * buttons.
+     */
+    va_start(ap, context);
+    i = 0;
+    while (va_arg(ap, char *) != NULL) {
+       i++;
+       if (c->radio.shortcut == NO_SHORTCUT)
+           va_arg(ap, int);           /* char promotes to int in arg lists */
+       va_arg(ap, intorptr);
+    }
+    va_end(ap);
+    c->radio.nbuttons = i;
+    if (c->radio.shortcut == NO_SHORTCUT)
+       c->radio.shortcuts = smalloc(c->radio.nbuttons * sizeof(char));
+    else
+       c->radio.shortcuts = NULL;
+    c->radio.buttons = smalloc(c->radio.nbuttons * sizeof(char *));
+    c->radio.buttondata = smalloc(c->radio.nbuttons * sizeof(intorptr));
+    /*
+     * Second pass along variable argument list to actually fill in
+     * the structure.
+     */
+    va_start(ap, context);
+    for (i = 0; i < c->radio.nbuttons; i++) {
+       c->radio.buttons[i] = dupstr(va_arg(ap, char *));
+       if (c->radio.shortcut == NO_SHORTCUT)
+           c->radio.shortcuts[i] = va_arg(ap, int);
+                                      /* char promotes to int in arg lists */
+       c->radio.buttondata[i] = va_arg(ap, intorptr);
+    }
+    va_end(ap);
+    return c;
+}
+
+union control *ctrl_pushbutton(struct controlset *s,char *label,char shortcut,
+                              intorptr helpctx, handler_fn handler,
+                              intorptr context)
+{
+    union control *c = ctrl_new(s, CTRL_BUTTON, helpctx, handler, context);
+    c->button.label = label ? dupstr(label) : NULL;
+    c->button.shortcut = shortcut;
+    c->button.isdefault = 0;
+    return c;
+}
+
+union control *ctrl_listbox(struct controlset *s,char *label,char shortcut,
+                           intorptr helpctx, handler_fn handler,
+                           intorptr context)
+{
+    union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
+    c->listbox.label = label ? dupstr(label) : NULL;
+    c->listbox.shortcut = shortcut;
+    c->listbox.height = 5;            /* *shrug* a plausible default */
+    c->listbox.draglist = 0;
+    c->listbox.multisel = 0;
+    c->listbox.percentwidth = 100;
+    c->listbox.ncols = 0;
+    c->listbox.percentages = NULL;
+    return c;
+}
+
+union control *ctrl_droplist(struct controlset *s, char *label, char shortcut,
+                            int percentage, intorptr helpctx,
+                            handler_fn handler, intorptr context)
+{
+    union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
+    c->listbox.label = label ? dupstr(label) : NULL;
+    c->listbox.shortcut = shortcut;
+    c->listbox.height = 0;            /* means it's a drop-down list */
+    c->listbox.draglist = 0;
+    c->listbox.multisel = 0;
+    c->listbox.percentwidth = percentage;
+    return c;
+}
+
+union control *ctrl_draglist(struct controlset *s,char *label,char shortcut,
+                            intorptr helpctx, handler_fn handler,
+                            intorptr context)
+{
+    union control *c = ctrl_new(s, CTRL_LISTBOX, helpctx, handler, context);
+    c->listbox.label = label ? dupstr(label) : NULL;
+    c->listbox.shortcut = shortcut;
+    c->listbox.height = 5;            /* *shrug* a plausible default */
+    c->listbox.draglist = 1;
+    c->listbox.multisel = 0;
+    c->listbox.percentwidth = 100;
+    return c;
+}
+
+union control *ctrl_filesel(struct controlset *s,char *label,char shortcut,
+                           char const *filter, int write, char *title,
+                           intorptr helpctx, handler_fn handler,
+                           intorptr context)
+{
+    union control *c = ctrl_new(s, CTRL_FILESELECT, helpctx, handler, context);
+    c->fileselect.label = label ? dupstr(label) : NULL;
+    c->fileselect.shortcut = shortcut;
+    c->fileselect.filter = filter;
+    c->fileselect.for_writing = write;
+    c->fileselect.title = dupstr(title);
+    return c;
+}
+
+union control *ctrl_fontsel(struct controlset *s,char *label,char shortcut,
+                           intorptr helpctx, handler_fn handler,
+                           intorptr context)
+{
+    union control *c = ctrl_new(s, CTRL_FONTSELECT, helpctx, handler, context);
+    c->fontselect.label = label ? dupstr(label) : NULL;
+    c->fontselect.shortcut = shortcut;
+    return c;
+}
+
+union control *ctrl_tabdelay(struct controlset *s, union control *ctrl)
+{
+    union control *c = ctrl_new(s, CTRL_TABDELAY, P(NULL), NULL, P(NULL));
+    c->tabdelay.ctrl = ctrl;
+    return c;
+}
+
+union control *ctrl_text(struct controlset *s, char *text, intorptr helpctx)
+{
+    union control *c = ctrl_new(s, CTRL_TEXT, helpctx, NULL, P(NULL));
+    c->text.label = dupstr(text);
+    return c;
+}
+
+union control *ctrl_checkbox(struct controlset *s, char *label, char shortcut,
+                            intorptr helpctx, handler_fn handler,
+                            intorptr context)
+{
+    union control *c = ctrl_new(s, CTRL_CHECKBOX, helpctx, handler, context);
+    c->checkbox.label = label ? dupstr(label) : NULL;
+    c->checkbox.shortcut = shortcut;
+    return c;
+}
+
+void ctrl_free(union control *ctrl)
+{
+    int i;
+
+    sfree(ctrl->generic.label);
+    switch (ctrl->generic.type) {
+      case CTRL_RADIO:
+       for (i = 0; i < ctrl->radio.nbuttons; i++)
+           sfree(ctrl->radio.buttons[i]);
+       sfree(ctrl->radio.buttons);
+       sfree(ctrl->radio.shortcuts);
+       sfree(ctrl->radio.buttondata);
+       break;
+      case CTRL_COLUMNS:
+       sfree(ctrl->columns.percentages);
+       break;
+      case CTRL_LISTBOX:
+       sfree(ctrl->listbox.percentages);
+       break;
+      case CTRL_FILESELECT:
+       sfree(ctrl->fileselect.title);
+       break;
+    }
+    sfree(ctrl);
+}
+
+void dlg_stdradiobutton_handler(union control *ctrl, void *dlg,
+                               void *data, int event)
+{
+    int button;
+    /*
+     * For a standard radio button set, the context parameter gives
+     * offsetof(targetfield, Config), and the extra data per button
+     * gives the value the target field should take if that button
+     * is the one selected.
+     */
+    if (event == EVENT_REFRESH) {
+       for (button = 0; button < ctrl->radio.nbuttons; button++)
+           if (*(int *)ATOFFSET(data, ctrl->radio.context.i) ==
+               ctrl->radio.buttondata[button].i)
+               break;
+       /* We expected that `break' to happen, in all circumstances. */
+       assert(button < ctrl->radio.nbuttons);
+       dlg_radiobutton_set(ctrl, dlg, button);
+    } else if (event == EVENT_VALCHANGE) {
+       button = dlg_radiobutton_get(ctrl, dlg);
+       assert(button >= 0 && button < ctrl->radio.nbuttons);
+       *(int *)ATOFFSET(data, ctrl->radio.context.i) =
+           ctrl->radio.buttondata[button].i;
+    }
+}
+
+void dlg_stdcheckbox_handler(union control *ctrl, void *dlg,
+                               void *data, int event)
+{
+    int offset, invert;
+
+    /*
+     * For a standard checkbox, the context parameter gives
+     * offsetof(targetfield, Config), optionally ORed with
+     * CHECKBOX_INVERT.
+     */
+    offset = ctrl->checkbox.context.i;
+    if (offset & CHECKBOX_INVERT) {
+       offset &= ~CHECKBOX_INVERT;
+       invert = 1;
+    } else
+       invert = 0;
+
+    /*
+     * C lacks a logical XOR, so the following code uses the idiom
+     * (!a ^ !b) to obtain the logical XOR of a and b. (That is, 1
+     * iff exactly one of a and b is nonzero, otherwise 0.)
+     */
+
+    if (event == EVENT_REFRESH) {
+       dlg_checkbox_set(ctrl,dlg, (!*(int *)ATOFFSET(data,offset) ^ !invert));
+    } else if (event == EVENT_VALCHANGE) {
+       *(int *)ATOFFSET(data, offset) = !dlg_checkbox_get(ctrl,dlg) ^ !invert;
+    }
+}
+
+void dlg_stdeditbox_handler(union control *ctrl, void *dlg,
+                           void *data, int event)
+{
+    /*
+     * The standard edit-box handler expects the main `context'
+     * field to contain the `offsetof' a field in the structure
+     * pointed to by `data'. The secondary `context2' field
+     * indicates the type of this field:
+     *
+     *  - if context2 > 0, the field is a char array and context2
+     *    gives its size.
+     *  - if context2 == -1, the field is an int and the edit box
+     *    is numeric.
+     *  - if context2 < -1, the field is an int and the edit box is
+     *    _floating_, and (-context2) gives the scale. (E.g. if
+     *    context2 == -1000, then typing 1.2 into the box will set
+     *    the field to 1200.)
+     */
+    int offset = ctrl->editbox.context.i;
+    int length = ctrl->editbox.context2.i;
+
+    if (length > 0) {
+       char *field = (char *)ATOFFSET(data, offset);
+       if (event == EVENT_REFRESH) {
+           dlg_editbox_set(ctrl, dlg, field);
+       } else if (event == EVENT_VALCHANGE) {
+           dlg_editbox_get(ctrl, dlg, field, length);
+       }
+    } else if (length < 0) {
+       int *field = (int *)ATOFFSET(data, offset);
+       char data[80];
+       if (event == EVENT_REFRESH) {
+           if (length == -1)
+               sprintf(data, "%d", *field);
+           else
+               sprintf(data, "%g", (double)*field / (double)(-length));
+           dlg_editbox_set(ctrl, dlg, data);
+       } else if (event == EVENT_VALCHANGE) {
+           dlg_editbox_get(ctrl, dlg, data, lenof(data));
+           if (length == -1)
+               *field = atoi(data);
+           else
+               *field = (int)((-length) * atof(data));
+       }
+    }
+}
+
+void dlg_stdfilesel_handler(union control *ctrl, void *dlg,
+                           void *data, int event)
+{
+    /*
+     * The standard file-selector handler expects the `context'
+     * field to contain the `offsetof' a Filename field in the
+     * structure pointed to by `data'.
+     */
+    int offset = ctrl->fileselect.context.i;
+
+    if (event == EVENT_REFRESH) {
+       dlg_filesel_set(ctrl, dlg, *(Filename *)ATOFFSET(data, offset));
+    } else if (event == EVENT_VALCHANGE) {
+       dlg_filesel_get(ctrl, dlg, (Filename *)ATOFFSET(data, offset));
+    }
+}
+
+void dlg_stdfontsel_handler(union control *ctrl, void *dlg,
+                           void *data, int event)
+{
+    /*
+     * The standard file-selector handler expects the `context'
+     * field to contain the `offsetof' a FontSpec field in the
+     * structure pointed to by `data'.
+     */
+    int offset = ctrl->fontselect.context.i;
+
+    if (event == EVENT_REFRESH) {
+       dlg_fontsel_set(ctrl, dlg, *(FontSpec *)ATOFFSET(data, offset));
+    } else if (event == EVENT_VALCHANGE) {
+       dlg_fontsel_get(ctrl, dlg, (FontSpec *)ATOFFSET(data, offset));
+    }
+}
diff --git a/dialog.h b/dialog.h
new file mode 100644 (file)
index 0000000..6b9f5e3
--- /dev/null
+++ b/dialog.h
@@ -0,0 +1,665 @@
+/*
+ * Exports and types from dialog.c.
+ */
+
+/*
+ * This will come in handy for generic control handlers. Anyone
+ * knows how to make this more portable, let me know :-)
+ */
+#define ATOFFSET(data, offset) ( (void *) ( (char *)(data) + (offset) ) )
+
+/*
+ * This is the big union which defines a single control, of any
+ * type.
+ * 
+ * General principles:
+ *  - _All_ pointers in this structure are expected to point to
+ *    dynamically allocated things, unless otherwise indicated.
+ *  - `char' fields giving keyboard shortcuts are expected to be
+ *    NO_SHORTCUT if no shortcut is desired for a particular control.
+ *  - The `label' field can often be NULL, which will cause the
+ *    control to not have a label at all. This doesn't apply to
+ *    checkboxes and push buttons, in which the label is not
+ *    separate from the control.
+ */
+
+#define NO_SHORTCUT '\0'
+
+enum {
+    CTRL_TEXT,                        /* just a static line of text */
+    CTRL_EDITBOX,                     /* label plus edit box */
+    CTRL_RADIO,                               /* label plus radio buttons */
+    CTRL_CHECKBOX,                    /* checkbox (contains own label) */
+    CTRL_BUTTON,                      /* simple push button (no label) */
+    CTRL_LISTBOX,                     /* label plus list box */
+    CTRL_COLUMNS,                     /* divide window into columns */
+    CTRL_FILESELECT,                  /* label plus filename selector */
+    CTRL_FONTSELECT,                  /* label plus font selector */
+    CTRL_TABDELAY                     /* see `tabdelay' below */
+};
+
+/*
+ * Many controls have `intorptr' unions for storing user data,
+ * since the user might reasonably want to store either an integer
+ * or a void * pointer. Here I define a union, and two convenience
+ * functions to create that union from actual integers or pointers.
+ * 
+ * The convenience functions are declared as inline if possible.
+ * Otherwise, they're declared here and defined when this header is
+ * included with DEFINE_INTORPTR_FNS defined. This is a total pain,
+ * but such is life.
+ */
+typedef union { void *p; int i; } intorptr;
+
+#if defined DEFINE_INTORPTR_FNS || defined INLINE
+#ifdef INLINE
+#define PREFIX INLINE
+#else
+#define PREFIX
+#endif
+PREFIX intorptr I(int i) { intorptr ret; ret.i = i; return ret; }
+PREFIX intorptr P(void *p) { intorptr ret; ret.p = p; return ret; }
+#undef PREFIX
+#else
+intorptr I(int i);
+intorptr P(void *p);
+#endif
+
+/*
+ * Each control has an `int' field specifying which columns it
+ * occupies in a multi-column part of the dialog box. These macros
+ * pack and unpack that field.
+ * 
+ * If a control belongs in exactly one column, just specifying the
+ * column number is perfectly adequate.
+ */
+#define COLUMN_FIELD(start, span) ( (((span)-1) << 16) + (start) )
+#define COLUMN_START(field) ( (field) & 0xFFFF )
+#define COLUMN_SPAN(field) ( (((field) >> 16) & 0xFFFF) + 1 )
+
+union control;
+
+/*
+ * The number of event types is being deliberately kept small, on
+ * the grounds that not all platforms might be able to report a
+ * large number of subtle events. We have:
+ *  - the special REFRESH event, called when a control's value
+ *    needs setting
+ *  - the ACTION event, called when the user does something that
+ *    positively requests action (double-clicking a list box item,
+ *    or pushing a push-button)
+ *  - the VALCHANGE event, called when the user alters the setting
+ *    of the control in a way that is usually considered to alter
+ *    the underlying data (toggling a checkbox or radio button,
+ *    moving the items around in a drag-list, editing an edit
+ *    control)
+ *  - the SELCHANGE event, called when the user alters the setting
+ *    of the control in a more minor way (changing the selected
+ *    item in a list box).
+ *  - the CALLBACK event, which happens after the handler routine
+ *    has requested a subdialog (file selector, font selector,
+ *    colour selector) and it has come back with information.
+ */
+enum {
+    EVENT_REFRESH,
+    EVENT_ACTION,
+    EVENT_VALCHANGE,
+    EVENT_SELCHANGE,
+    EVENT_CALLBACK
+};
+typedef void (*handler_fn)(union control *ctrl, void *dlg,
+                          void *data, int event);
+
+#define STANDARD_PREFIX \
+       int type; \
+       char *label; \
+       int tabdelay; \
+       int column; \
+        handler_fn handler; \
+       intorptr context; \
+        intorptr helpctx
+
+union control {
+    /*
+     * The first possibility in this union is the generic header
+     * shared by all the structures, which we are therefore allowed
+     * to access through any one of them.
+     */
+    struct {
+       int type;
+       /*
+        * Every control except CTRL_COLUMNS has _some_ sort of
+        * label. By putting it in the `generic' union as well as
+        * everywhere else, we avoid having to have an irritating
+        * switch statement when we go through and deallocate all
+        * the memory in a config-box structure.
+        * 
+        * Yes, this does mean that any non-NULL value in this
+        * field is expected to be dynamically allocated and
+        * freeable.
+        * 
+        * For CTRL_COLUMNS, this field MUST be NULL.
+        */
+       char *label;
+       /*
+        * If `tabdelay' is non-zero, it indicates that this
+        * particular control should not yet appear in the tab
+        * order. A subsequent CTRL_TABDELAY entry will place it.
+        */
+       int tabdelay;
+       /*
+        * Indicate which column(s) this control occupies. This can
+        * be unpacked into starting column and column span by the
+        * COLUMN macros above.
+        */
+       int column;
+       /*
+        * Most controls need to provide a function which gets
+        * called when that control's setting is changed, or when
+        * the control's setting needs initialising.
+        * 
+        * The `data' parameter points to the writable data being
+        * modified as a result of the configuration activity; for
+        * example, the PuTTY `Config' structure, although not
+        * necessarily.
+        * 
+        * The `dlg' parameter is passed back to the platform-
+        * specific routines to read and write the actual control
+        * state.
+        */
+       handler_fn handler;
+       /*
+        * Almost all of the above functions will find it useful to
+        * be able to store a piece of `void *' or `int' data.
+        */
+       intorptr context;
+       /*
+        * For any control, we also allow the storage of a piece of
+        * data for use by context-sensitive help. For example, on
+        * Windows you can click the magic question mark and then
+        * click a control, and help for that control should spring
+        * up. Hence, here is a slot in which to store per-control
+        * data that a particular platform-specific driver can use
+        * to ensure it brings up the right piece of help text.
+        */
+       intorptr helpctx;
+    } generic;
+    struct {
+       STANDARD_PREFIX;
+       union control *ctrl;
+    } tabdelay;
+    struct {
+       STANDARD_PREFIX;
+    } text;
+    struct {
+       STANDARD_PREFIX;
+       char shortcut;                 /* keyboard shortcut */
+       /*
+        * Percentage of the dialog-box width used by the edit box.
+        * If this is set to 100, the label is on its own line;
+        * otherwise the label is on the same line as the box
+        * itself.
+        */
+       int percentwidth;
+       int password;                  /* details of input are hidden */
+       /*
+        * A special case of the edit box is the combo box, which
+        * has a drop-down list built in. (Note that a _non_-
+        * editable drop-down list is done as a special case of a
+        * list box.)
+        */
+       int has_list;
+       /*
+        * Edit boxes tend to need two items of context, so here's
+        * a spare.
+        */
+       intorptr context2;
+    } editbox;
+    struct {
+       STANDARD_PREFIX;
+       /*
+        * `shortcut' here is a single keyboard shortcut which is
+        * expected to select the whole group of radio buttons. It
+        * can be NO_SHORTCUT if required, and there is also a way
+        * to place individual shortcuts on each button; see below.
+        */
+       char shortcut;
+       /*
+        * There are separate fields for `ncolumns' and `nbuttons'
+        * for several reasons.
+        * 
+        * Firstly, we sometimes want the last of a set of buttons
+        * to have a longer label than the rest; we achieve this by
+        * setting `ncolumns' higher than `nbuttons', and the
+        * layout code is expected to understand that the final
+        * button should be given all the remaining space on the
+        * line. This sounds like a ludicrously specific special
+        * case (if we're doing this sort of thing, why not have
+        * the general ability to have a particular button span
+        * more than one column whether it's the last one or not?)
+        * but actually it's reasonably common for the sort of
+        * three-way control you get a lot of in PuTTY: `yes'
+        * versus `no' versus `some more complex way to decide'.
+        * 
+        * Secondly, setting `nbuttons' higher than `ncolumns' lets
+        * us have more than one line of radio buttons for a single
+        * setting. A very important special case of this is
+        * setting `ncolumns' to 1, so that each button is on its
+        * own line.
+        */
+       int ncolumns;
+       int nbuttons;
+       /*
+        * This points to a dynamically allocated array of `char *'
+        * pointers, each of which points to a dynamically
+        * allocated string.
+        */
+       char **buttons;                /* `nbuttons' button labels */
+       /*
+        * This points to a dynamically allocated array of `char'
+        * giving the individual keyboard shortcuts for each radio
+        * button. The array may be NULL if none are required.
+        */
+       char *shortcuts;               /* `nbuttons' shortcuts; may be NULL */
+       /*
+        * This points to a dynamically allocated array of
+        * intorptr, giving helpful data for each button.
+        */
+       intorptr *buttondata;          /* `nbuttons' entries; may be NULL */
+    } radio;
+    struct {
+       STANDARD_PREFIX;
+       char shortcut;
+    } checkbox;
+    struct {
+       STANDARD_PREFIX;
+       char shortcut;
+       /*
+        * At least Windows has the concept of a `default push
+        * button', which gets implicitly pressed when you hit
+        * Return even if it doesn't have the input focus.
+        */
+       int isdefault;
+    } button;
+    struct {
+       STANDARD_PREFIX;
+       char shortcut;                 /* keyboard shortcut */
+       /*
+        * Height of the list box, in approximate number of lines.
+        * If this is zero, the list is a drop-down list.
+        */
+       int height;                    /* height in lines */
+       /*
+        * If this is set, the list elements can be reordered by
+        * the user (by drag-and-drop or by Up and Down buttons,
+        * whatever the per-platform implementation feels
+        * comfortable with). This is not guaranteed to work on a
+        * drop-down list, so don't try it!
+        */
+       int draglist;
+       /*
+        * If this is set, the list can have more than one element
+        * selected at a time. This is not guaranteed to work on a
+        * drop-down list, so don't try it!
+        */
+       int multisel;
+       /*
+        * Percentage of the dialog-box width used by the list box.
+        * If this is set to 100, the label is on its own line;
+        * otherwise the label is on the same line as the box
+        * itself. Setting this to anything other than 100 is not
+        * guaranteed to work on a _non_-drop-down list, so don't
+        * try it!
+        */
+       int percentwidth;
+       /*
+        * Some list boxes contain strings that contain tab
+        * characters. If `ncols' is greater than 0, then
+        * `percentages' is expected to be non-zero and to contain
+        * the respective widths of `ncols' columns, which together
+        * will exactly fit the width of the list box. Otherwise
+        * `percentages' must be NULL.
+        */
+       int ncols;                     /* number of columns */
+       int *percentages;              /* % width of each column */
+    } listbox;
+    struct {
+       STANDARD_PREFIX;
+       char shortcut;
+       /*
+        * `filter' dictates what type of files will be selected by
+        * default; for example, when selecting private key files
+        * the file selector would do well to only show .PPK files
+        * (on those systems where this is the chosen extension).
+        * 
+        * The precise contents of `filter' are platform-defined,
+        * unfortunately. The special value NULL means `all files'
+        * and is always a valid fallback.
+        * 
+        * Unlike almost all strings in this structure, this value
+        * is NOT expected to require freeing (although of course
+        * you can always use ctrl_alloc if you do need to create
+        * one on the fly). This is because the likely mode of use
+        * is to define string constants in a platform-specific
+        * header file, and directly reference those. Or worse, a
+        * particular platform might choose to cast integers into
+        * this pointer type...
+        */
+       char const *filter;
+       /*
+        * Some systems like to know whether a file selector is
+        * choosing a file to read or one to write (and possibly
+        * create).
+        */
+       int for_writing;
+       /*
+        * On at least some platforms, the file selector is a
+        * separate dialog box, and contains a user-settable title.
+        * 
+        * This value _is_ expected to require freeing.
+        */
+       char *title;
+    } fileselect;
+    struct {
+       /* In this variant, `label' MUST be NULL. */
+       STANDARD_PREFIX;
+       int ncols;                     /* number of columns */
+       int *percentages;              /* % width of each column */
+       /*
+        * Every time this control type appears, exactly one of
+        * `ncols' and the previous number of columns MUST be one.
+        * Attempting to allow a seamless transition from a four-
+        * to a five-column layout, for example, would be way more
+        * trouble than it was worth. If you must lay things out
+        * like that, define eight unevenly sized columns and use
+        * column-spanning a lot. But better still, just don't.
+        * 
+        * `percentages' may be NULL if ncols==1, to save space.
+        */
+    } columns;
+    struct {
+       STANDARD_PREFIX;
+       char shortcut;
+    } fontselect;
+};
+
+#undef STANDARD_PREFIX
+
+/*
+ * `controlset' is a container holding an array of `union control'
+ * structures, together with a panel name and a title for the whole
+ * set. In Windows and any similar-looking GUI, each `controlset'
+ * in the config will be a container box within a panel.
+ * 
+ * Special case: if `boxname' is NULL, the control set gives an
+ * overall title for an entire panel of controls.
+ */
+struct controlset {
+    char *pathname;                   /* panel path, e.g. "SSH/Tunnels" */
+    char *boxname;                    /* internal short name of controlset */
+    char *boxtitle;                   /* title of container box */
+    int ncolumns;                     /* current no. of columns at bottom */
+    int ncontrols;                    /* number of `union control' in array */
+    int ctrlsize;                     /* allocated size of array */
+    union control **ctrls;            /* actual array */
+};
+
+/*
+ * This is the container structure which holds a complete set of
+ * controls.
+ */
+struct controlbox {
+    int nctrlsets;                    /* number of ctrlsets */
+    int ctrlsetsize;                  /* ctrlset size */
+    struct controlset **ctrlsets;      /* actual array of ctrlsets */
+    int nfrees;
+    int freesize;
+    void **frees;                     /* array of aux data areas to free */
+};
+
+struct controlbox *ctrl_new_box(void);
+void ctrl_free_box(struct controlbox *);
+
+/*
+ * Standard functions used for populating a controlbox structure.
+ */
+
+/* Set up a panel title. */
+struct controlset *ctrl_settitle(struct controlbox *,
+                                char *path, char *title);
+/* Retrieve a pointer to a controlset, creating it if absent. */
+struct controlset *ctrl_getset(struct controlbox *,
+                              char *path, char *name, char *boxtitle);
+void ctrl_free_set(struct controlset *);
+
+void ctrl_free(union control *);
+
+/*
+ * This function works like `malloc', but the memory it returns
+ * will be automatically freed when the controlbox is freed. Note
+ * that a controlbox is a dialog-box _template_, not an instance,
+ * and so data allocated through this function is better not used
+ * to hold modifiable per-instance things. It's mostly here for
+ * allocating structures to be passed as control handler params.
+ */
+void *ctrl_alloc(struct controlbox *b, size_t size);
+
+/*
+ * Individual routines to create `union control' structures in a controlset.
+ * 
+ * Most of these routines allow the most common fields to be set
+ * directly, and put default values in the rest. Each one returns a
+ * pointer to the `union control' it created, so that final tweaks
+ * can be made.
+ */
+
+/* `ncolumns' is followed by that many percentages, as integers. */
+union control *ctrl_columns(struct controlset *, int ncolumns, ...);
+union control *ctrl_editbox(struct controlset *, char *label, char shortcut,
+                           int percentage, intorptr helpctx,
+                           handler_fn handler,
+                           intorptr context, intorptr context2);
+union control *ctrl_combobox(struct controlset *, char *label, char shortcut,
+                            int percentage, intorptr helpctx,
+                            handler_fn handler,
+                            intorptr context, intorptr context2);
+/*
+ * `ncolumns' is followed by (alternately) radio button titles and
+ * intorptrs, until a NULL in place of a title string is seen. Each
+ * title is expected to be followed by a shortcut _iff_ `shortcut'
+ * is NO_SHORTCUT.
+ */
+union control *ctrl_radiobuttons(struct controlset *, char *label,
+                                char shortcut, int ncolumns,
+                                intorptr helpctx,
+                                handler_fn handler, intorptr context, ...);
+union control *ctrl_pushbutton(struct controlset *,char *label,char shortcut,
+                              intorptr helpctx,
+                              handler_fn handler, intorptr context);
+union control *ctrl_listbox(struct controlset *,char *label,char shortcut,
+                           intorptr helpctx,
+                           handler_fn handler, intorptr context);
+union control *ctrl_droplist(struct controlset *, char *label, char shortcut,
+                            int percentage, intorptr helpctx,
+                            handler_fn handler, intorptr context);
+union control *ctrl_draglist(struct controlset *,char *label,char shortcut,
+                            intorptr helpctx,
+                            handler_fn handler, intorptr context);
+union control *ctrl_filesel(struct controlset *,char *label,char shortcut,
+                           char const *filter, int write, char *title,
+                           intorptr helpctx,
+                           handler_fn handler, intorptr context);
+union control *ctrl_fontsel(struct controlset *,char *label,char shortcut,
+                           intorptr helpctx,
+                           handler_fn handler, intorptr context);
+union control *ctrl_text(struct controlset *, char *text, intorptr helpctx);
+union control *ctrl_checkbox(struct controlset *, char *label, char shortcut,
+                            intorptr helpctx,
+                            handler_fn handler, intorptr context);
+union control *ctrl_tabdelay(struct controlset *, union control *);
+
+/*
+ * Standard handler routines to cover most of the common cases in
+ * the config box.
+ */
+/*
+ * The standard radio-button handler expects the main `context'
+ * field to contain the `offsetof' of an int field in the structure
+ * pointed to by `data', and expects each of the individual button
+ * data to give a value for that int field.
+ */
+void dlg_stdradiobutton_handler(union control *ctrl, void *dlg,
+                               void *data, int event);
+/*
+ * The standard checkbox handler expects the main `context' field
+ * to contain the `offsetof' an int field in the structure pointed
+ * to by `data', optionally ORed with CHECKBOX_INVERT to indicate
+ * that the sense of the datum is opposite to the sense of the
+ * checkbox.
+ */
+#define CHECKBOX_INVERT (1<<30)
+void dlg_stdcheckbox_handler(union control *ctrl, void *dlg,
+                            void *data, int event);
+/*
+ * The standard edit-box handler expects the main `context' field
+ * to contain the `offsetof' a field in the structure pointed to by
+ * `data'. The secondary `context2' field indicates the type of
+ * this field:
+ * 
+ *  - if context2 > 0, the field is a char array and context2 gives
+ *    its size.
+ *  - if context2 == -1, the field is an int and the edit box is
+ *    numeric.
+ *  - if context2 < -1, the field is an int and the edit box is
+ *    _floating_, and (-context2) gives the scale. (E.g. if
+ *    context2 == -1000, then typing 1.2 into the box will set the
+ *    field to 1200.)
+ */
+void dlg_stdeditbox_handler(union control *ctrl, void *dlg,
+                           void *data, int event);
+/*
+ * The standard file-selector handler expects the main `context'
+ * field to contain the `offsetof' a Filename field in the
+ * structure pointed to by `data'.
+ */
+void dlg_stdfilesel_handler(union control *ctrl, void *dlg,
+                           void *data, int event);
+/*
+ * The standard font-selector handler expects the main `context'
+ * field to contain the `offsetof' a Font field in the structure
+ * pointed to by `data'.
+ */
+void dlg_stdfontsel_handler(union control *ctrl, void *dlg,
+                           void *data, int event);
+
+/*
+ * Routines the platform-independent dialog code can call to read
+ * and write the values of controls.
+ */
+void dlg_radiobutton_set(union control *ctrl, void *dlg, int whichbutton);
+int dlg_radiobutton_get(union control *ctrl, void *dlg);
+void dlg_checkbox_set(union control *ctrl, void *dlg, int checked);
+int dlg_checkbox_get(union control *ctrl, void *dlg);
+void dlg_editbox_set(union control *ctrl, void *dlg, char const *text);
+void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length);
+/* The `listbox' functions can also apply to combo boxes. */
+void dlg_listbox_clear(union control *ctrl, void *dlg);
+void dlg_listbox_del(union control *ctrl, void *dlg, int index);
+void dlg_listbox_add(union control *ctrl, void *dlg, char const *text);
+/*
+ * Each listbox entry may have a numeric id associated with it.
+ * Note that some front ends only permit a string to be stored at
+ * each position, which means that _if_ you put two identical
+ * strings in any listbox then you MUST not assign them different
+ * IDs and expect to get meaningful results back.
+ */
+void dlg_listbox_addwithindex(union control *ctrl, void *dlg,
+                             char const *text, int id);
+int dlg_listbox_getid(union control *ctrl, void *dlg, int index);
+/* dlg_listbox_index returns <0 if no single element is selected. */
+int dlg_listbox_index(union control *ctrl, void *dlg);
+int dlg_listbox_issel(union control *ctrl, void *dlg, int index);
+void dlg_listbox_select(union control *ctrl, void *dlg, int index);
+void dlg_text_set(union control *ctrl, void *dlg, char const *text);
+void dlg_filesel_set(union control *ctrl, void *dlg, Filename fn);
+void dlg_filesel_get(union control *ctrl, void *dlg, Filename *fn);
+void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec fn);
+void dlg_fontsel_get(union control *ctrl, void *dlg, FontSpec *fn);
+/*
+ * Bracketing a large set of updates in these two functions will
+ * cause the front end (if possible) to delay updating the screen
+ * until it's all complete, thus avoiding flicker.
+ */
+void dlg_update_start(union control *ctrl, void *dlg);
+void dlg_update_done(union control *ctrl, void *dlg);
+/*
+ * Set input focus into a particular control.
+ */
+void dlg_set_focus(union control *ctrl, void *dlg);
+/*
+ * Return the `ctrl' structure for the control that had the input
+ * focus before this one. This is NOT GUARANTEED to work on all
+ * platforms, so don't base any critical functionality on it!
+ */
+union control *dlg_last_focused(void *dlg);
+/*
+ * During event processing, you might well want to give an error
+ * indication to the user. dlg_beep() is a quick and easy generic
+ * error; dlg_error() puts up a message-box or equivalent.
+ */
+void dlg_beep(void *dlg);
+void dlg_error_msg(void *dlg, char *msg);
+/*
+ * This function signals to the front end that the dialog's
+ * processing is completed, and passes an integer value (typically
+ * a success status).
+ */
+void dlg_end(void *dlg, int value);
+
+/*
+ * Routines to manage a (per-platform) colour selector.
+ * dlg_coloursel_start() is called in an event handler, and
+ * schedules the running of a colour selector after the event
+ * handler returns. The colour selector will send EVENT_CALLBACK to
+ * the control that spawned it, when it's finished;
+ * dlg_coloursel_results() fetches the results, as integers from 0
+ * to 255; it returns nonzero on success, or zero if the colour
+ * selector was dismissed by hitting Cancel or similar.
+ * 
+ * dlg_coloursel_start() accepts an RGB triple which is used to
+ * initialise the colour selector to its starting value.
+ */
+void dlg_coloursel_start(union control *ctrl, void *dlg,
+                        int r, int g, int b);
+int dlg_coloursel_results(union control *ctrl, void *dlg,
+                         int *r, int *g, int *b);
+
+/*
+ * This routine is used by the platform-independent code to
+ * indicate that the value of a particular control is likely to
+ * have changed. It triggers a call of the handler for that control
+ * with `event' set to EVENT_REFRESH.
+ * 
+ * If `ctrl' is NULL, _all_ controls in the dialog get refreshed
+ * (for loading or saving entire sets of settings).
+ */
+void dlg_refresh(union control *ctrl, void *dlg);
+
+/*
+ * Standard helper functions for reading a controlbox structure.
+ */
+
+/*
+ * Find the index of next controlset in a controlbox for a given
+ * path, or -1 if no such controlset exists. If -1 is passed as
+ * input, finds the first. Intended usage is something like
+ * 
+ *     for (index=-1; (index=ctrl_find_path(ctrlbox, index, path)) >= 0 ;) {
+ *          ... process this controlset ...
+ *      }
+ */
+int ctrl_find_path(struct controlbox *b, char *path, int index);
+int ctrl_path_elements(char *path);
+/* Return the number of matching path elements at the starts of p1 and p2,
+ * or INT_MAX if the paths are identical. */
+int ctrl_path_compare(char *p1, char *p2);
index f96d257..4bf4c25 100644 (file)
@@ -1,4 +1,4 @@
-\versionid $Id: config.but,v 1.56 2003/02/19 09:54:45 jacob Exp $
+\versionid $Id: config.but,v 1.57 2003/03/05 22:07:40 simon Exp $
 
 \C{config} Configuring PuTTY
 
@@ -622,10 +622,14 @@ on a terminal bell:
 the server can send as many Control-G characters as it likes and
 nothing at all will happen.
 
-\b \q{Play Windows Default Sound} is the default setting. It causes
-the Windows \q{Default Beep} sound to be played. To change what this
-sound is, or to test it if nothing seems to be happening, use the
-Sound configurer in the Windows Control Panel.
+\b \q{Make default system alert sound} is the default setting. It
+causes the Windows \q{Default Beep} sound to be played. To change
+what this sound is, or to test it if nothing seems to be happening,
+use the Sound configurer in the Windows Control Panel.
+
+\b \q{Visual bell} is a silent alternative to a beeping computer. In
+this mode, when the server sends a Control-G, the whole PuTTY window
+will flash white for a fraction of a second.
 
 \b \q{Play a custom sound file} allows you to specify a particular
 sound file to be used by PuTTY alone, or even by a particular
@@ -634,10 +638,6 @@ beeps from any other beeps on the system. If you select this option,
 you will also need to enter the name of your sound file in the edit
 control \q{Custom sound file to play as a bell}.
 
-\b \q{Visual bell} is a silent alternative to a beeping computer. In
-this mode, when the server sends a Control-G, the whole PuTTY window
-will flash white for a fraction of a second.
-
 \S{config-belltaskbar} \q{Taskbar/caption indication on bell}
 
 \cfg{winhelp-topic}{bell.taskbar}
@@ -883,32 +883,6 @@ offered a choice from all the fixed-width fonts installed on the
 system. (VT100-style terminal handling can only deal with fixed-
 width fonts.)
 
-\S{config-title} Controlling the window title
-
-\cfg{winhelp-topic}{appearance.title}
-
-The \q{Window title} edit box allows you to set the title of the
-PuTTY window. By default the window title will contain the host name
-followed by \q{PuTTY}, for example \c{server1.example.com - PuTTY}.
-If you want a different window title, this is where to set it.
-
-PuTTY allows the server to send \c{xterm} control sequences which
-modify the title of the window in mid-session. There is also an
-\c{xterm} sequence to modify the title of the window's \e{icon}.
-This makes sense in a windowing system where the window becomes an
-icon when minimised, such as Windows 3.1 or most X Window System
-setups; but in the Windows 95-like user interface it isn't as
-applicable.
-
-By default, PuTTY only uses the server-supplied \e{window} title, and
-ignores the icon title entirely. If for some reason you want to see
-both titles, check the box marked \q{Separate window and icon titles}.
-If you do this, PuTTY's window title and Taskbar caption will
-change into the server-supplied icon title if you minimise the PuTTY
-window, and change back to the server-supplied window title if you
-restore it. (If the server has not bothered to supply a window or
-icon title, none of this will happen.)
-
 \S{config-mouseptr} \q{Hide mouse pointer when typing in window}
 
 \cfg{winhelp-topic}{appearance.hidemouse}
@@ -944,6 +918,32 @@ it to zero, or increase it further.
 The Behaviour configuration panel allows you to control aspects of
 the behaviour of PuTTY's window.
 
+\S{config-title} Controlling the window title
+
+\cfg{winhelp-topic}{appearance.title}
+
+The \q{Window title} edit box allows you to set the title of the
+PuTTY window. By default the window title will contain the host name
+followed by \q{PuTTY}, for example \c{server1.example.com - PuTTY}.
+If you want a different window title, this is where to set it.
+
+PuTTY allows the server to send \c{xterm} control sequences which
+modify the title of the window in mid-session. There is also an
+\c{xterm} sequence to modify the title of the window's \e{icon}.
+This makes sense in a windowing system where the window becomes an
+icon when minimised, such as Windows 3.1 or most X Window System
+setups; but in the Windows 95-like user interface it isn't as
+applicable.
+
+By default, PuTTY only uses the server-supplied \e{window} title, and
+ignores the icon title entirely. If for some reason you want to see
+both titles, check the box marked \q{Separate window and icon titles}.
+If you do this, PuTTY's window title and Taskbar caption will
+change into the server-supplied icon title if you minimise the PuTTY
+window, and change back to the server-supplied window title if you
+restore it. (If the server has not bothered to supply a window or
+icon title, none of this will happen.)
+
 \S{config-warnonclose} \q{Warn before closing window}
 
 \cfg{winhelp-topic}{behaviour.closewarn}
index 28e36c7..4c42471 100644 (file)
@@ -48,20 +48,6 @@ STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
 CAPTION "PuTTY Configuration"
 FONT 8, "MS Shell Dlg"
 BEGIN
-    DEFPUSHBUTTON "&Open", IDOK, 184, 235, 44, 14
-    PUSHBUTTON "&Cancel", IDCANCEL, 231, 235, 44, 14
-    PUSHBUTTON "&About", IDC_ABOUT, 3, 235, 44, 14, NOT WS_TABSTOP
-    PUSHBUTTON "&Help", IDC_HELPBTN, 50, 235, 44, 14, NOT WS_TABSTOP
-END
-
-/* Accelerators used: ac */
-IDD_RECONF DIALOG DISCARDABLE 0, 0, 280, 252
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
-CAPTION "PuTTY Reconfiguration"
-FONT 8, "MS Shell Dlg"
-BEGIN
-    DEFPUSHBUTTON "&Apply", IDOK, 184, 235, 44, 14
-    PUSHBUTTON "&Cancel", IDCANCEL, 231, 235, 44, 14
 END
 
 /* Accelerators used: co */
diff --git a/wincfg.c b/wincfg.c
new file mode 100644 (file)
index 0000000..5b5bf9a
--- /dev/null
+++ b/wincfg.c
@@ -0,0 +1,275 @@
+/*
+ * wincfg.c - the Windows-specific parts of the PuTTY configuration
+ * box.
+ */
+
+#include <windows.h>
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "putty.h"
+#include "dialog.h"
+#include "storage.h"
+
+static void about_handler(union control *ctrl, void *dlg,
+                         void *data, int event)
+{
+    HWND *hwndp = (HWND *)ctrl->generic.context.p;
+
+    if (event == EVENT_ACTION) {
+       modal_about_box(*hwndp);
+    }
+}
+
+static void help_handler(union control *ctrl, void *dlg,
+                        void *data, int event)
+{
+    HWND *hwndp = (HWND *)ctrl->generic.context.p;
+
+    if (event == EVENT_ACTION) {
+       show_help(*hwndp);
+    }
+}
+
+void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help,
+                         int midsession)
+{
+    struct controlset *s;
+    union control *c;
+
+    if (!midsession) {
+       /*
+        * Add the About and Help buttons to the standard panel.
+        */
+       s = ctrl_getset(b, "", "", "");
+       c = ctrl_pushbutton(s, "About", 'a', HELPCTX(no_help),
+                           about_handler, P(hwndp));
+       c->generic.column = 0;
+       if (has_help) {
+           c = ctrl_pushbutton(s, "Help", 'h', HELPCTX(no_help),
+                               help_handler, P(hwndp));
+           c->generic.column = 1;
+       }
+    }
+
+    /*
+     * Windows has the AltGr key, which has various Windows-
+     * specific options.
+     */
+    s = ctrl_getset(b, "Terminal/Keyboard", "features",
+                   "Enable extra keyboard features:");
+    ctrl_checkbox(s, "AltGr acts as Compose key", 't',
+                 HELPCTX(keyboard_compose),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,compose_key)));
+    ctrl_checkbox(s, "Control-Alt is different from AltGr", 'd',
+                 HELPCTX(keyboard_ctrlalt),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,ctrlaltkeys)));
+
+    /*
+     * Windows allows an arbitrary .WAV to be played as a bell. For
+     * this we must search the existing controlset for the
+     * radio-button set controlling the `beep' option, and add an
+     * extra button to it.
+     * 
+     * Note that although this _looks_ like a hideous hack, it's
+     * actually all above board. The well-defined interface to the
+     * per-platform dialog box code is the _data structures_ `union
+     * control', `struct controlset' and so on; so code like this
+     * that reaches into those data structures and changes bits of
+     * them is perfectly legitimate and crosses no boundaries. All
+     * the ctrl_* routines that create most of the controls are
+     * convenient shortcuts provided on the cross-platform side of
+     * the interface, and template creation code is under no actual
+     * obligation to use them.
+     */
+    s = ctrl_getset(b, "Terminal/Bell", "style", "Set the style of bell");
+    {
+       int i;
+       for (i = 0; i < s->ncontrols; i++) {
+           c = s->ctrls[i];
+           if (c->generic.type == CTRL_RADIO &&
+               c->generic.context.i == offsetof(Config, beep)) {
+               assert(c->generic.handler == dlg_stdradiobutton_handler);
+               c->radio.nbuttons++;
+               c->radio.buttons =
+                   srealloc(c->radio.buttons,
+                            c->radio.nbuttons * sizeof(*c->radio.buttons));
+               c->radio.buttons[c->radio.nbuttons-1] =
+                   dupstr("Play a custom sound file");
+               c->radio.buttondata =
+                   srealloc(c->radio.buttondata,
+                            c->radio.nbuttons * sizeof(*c->radio.buttondata));
+               c->radio.buttondata[c->radio.nbuttons-1] = I(BELL_WAVEFILE);
+               if (c->radio.shortcuts) {
+                   c->radio.shortcuts =
+                       srealloc(c->radio.shortcuts,
+                                (c->radio.nbuttons *
+                                 sizeof(*c->radio.shortcuts)));
+                   c->radio.shortcuts[c->radio.nbuttons-1] = NO_SHORTCUT;
+               }
+               break;
+           }
+       }
+    }
+    ctrl_filesel(s, "Custom sound file to play as a bell:", NO_SHORTCUT,
+                FILTER_WAVE_FILES, FALSE, "Select bell sound file",
+                HELPCTX(bell_style),
+                dlg_stdfilesel_handler, I(offsetof(Config, bell_wavefile)));
+
+    /*
+     * While we've got this box open, taskbar flashing on a bell is
+     * also Windows-specific.
+     */
+    ctrl_radiobuttons(s, "Taskbar/caption indication on bell:", 'i', 3,
+                     HELPCTX(bell_taskbar),
+                     dlg_stdradiobutton_handler,
+                     I(offsetof(Config, beep_ind)),
+                     "Disabled", I(B_IND_DISABLED),
+                     "Flashing", I(B_IND_FLASH),
+                     "Steady", I(B_IND_STEADY), NULL);
+
+    /*
+     * The sunken-edge border is a Windows GUI feature.
+     */
+    s = ctrl_getset(b, "Window/Appearance", "border",
+                   "Adjust the window border");
+    ctrl_checkbox(s, "Sunken-edge border (slightly thicker)", 's',
+                 HELPCTX(appearance_border),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,sunken_edge)));
+
+    /*
+     * Cyrillic Lock is a horrid misfeature even on Windows, and
+     * the least we can do is ensure it never makes it to any other
+     * platform (at least unless someone fixes it!).
+     */
+    s = ctrl_getset(b, "Window/Translation", "input",
+                   "Enable character set translation on input data");
+    ctrl_checkbox(s, "Caps Lock acts as Cyrillic switch", 's',
+                 HELPCTX(translation_cyrillic),
+                 dlg_stdcheckbox_handler,
+                 I(offsetof(Config,xlat_capslockcyr)));
+
+    /*
+     * Windows has the weird OEM font mode, which gives us some
+     * additional options when working with line-drawing
+     * characters.
+     */
+    s = ctrl_getset(b, "Window/Translation", "linedraw",
+                   "Adjust how PuTTY displays line drawing characters");
+    {
+       int i;
+       for (i = 0; i < s->ncontrols; i++) {
+           c = s->ctrls[i];
+           if (c->generic.type == CTRL_RADIO &&
+               c->generic.context.i == offsetof(Config, vtmode)) {
+               assert(c->generic.handler == dlg_stdradiobutton_handler);
+               c->radio.nbuttons += 2;
+               c->radio.buttons =
+                   srealloc(c->radio.buttons,
+                            c->radio.nbuttons * sizeof(*c->radio.buttons));
+               c->radio.buttons[c->radio.nbuttons-2] =
+                   dupstr("Use font in both ANSI and OEM modes");
+               c->radio.buttons[c->radio.nbuttons-1] =
+                   dupstr("Use font in OEM mode only");
+               c->radio.buttondata =
+                   srealloc(c->radio.buttondata,
+                            c->radio.nbuttons * sizeof(*c->radio.buttondata));
+               c->radio.buttondata[c->radio.nbuttons-2] = I(VT_OEMANSI);
+               c->radio.buttondata[c->radio.nbuttons-1] = I(VT_OEMONLY);
+               if (!c->radio.shortcuts) {
+                   int j;
+                   c->radio.shortcuts =
+                       smalloc((c->radio.nbuttons *
+                                sizeof(*c->radio.shortcuts)));
+                   for (j = 0; j < c->radio.nbuttons; j++)
+                       c->radio.shortcuts[j] = NO_SHORTCUT;
+               } else {
+                   c->radio.shortcuts =
+                       srealloc(c->radio.shortcuts,
+                                (c->radio.nbuttons *
+                                 sizeof(*c->radio.shortcuts)));
+               }
+               c->radio.shortcuts[c->radio.nbuttons-2] = 'b';
+               c->radio.shortcuts[c->radio.nbuttons-1] = 'e';
+               break;
+           }
+       }
+    }
+
+    /*
+     * RTF paste is Windows-specific.
+     */
+    s = ctrl_getset(b, "Window/Selection", "trans",
+                   "Translation of pasted characters");
+    ctrl_checkbox(s, "Paste to clipboard in RTF as well as plain text", 'f',
+                 HELPCTX(selection_rtf),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,rtf_paste)));
+
+    /*
+     * Windows often has no middle button, so we supply a selection
+     * mode in which the more critical Paste action is available on
+     * the right button instead.
+     */
+    s = ctrl_getset(b, "Window/Selection", "mouse",
+                   "Control use of mouse");
+    ctrl_radiobuttons(s, "Action of mouse buttons:", NO_SHORTCUT, 1,
+                     HELPCTX(selection_buttons),
+                     dlg_stdradiobutton_handler,
+                     I(offsetof(Config, mouse_is_xterm)),
+                     "Windows (Right pastes, Middle extends)", 'w', I(0),
+                     "xterm (Right extends, Middle pastes)", 'x', I(1), NULL);
+    /*
+     * This really ought to go at the _top_ of its box, not the
+     * bottom, so we'll just do some shuffling now we've set it
+     * up...
+     */
+    c = s->ctrls[s->ncontrols-1];      /* this should be the new control */
+    memmove(s->ctrls+1, s->ctrls, (s->ncontrols-1)*sizeof(union control *));
+    s->ctrls[0] = c;
+
+    /*
+     * Logical palettes don't even make sense anywhere except Windows.
+     */
+    s = ctrl_getset(b, "Window/Colours", "general",
+                   "General options for colour usage");
+    ctrl_checkbox(s, "Attempt to use logical palettes", 'l',
+                 HELPCTX(colours_logpal),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,try_palette)));
+
+    /*
+     * Resize-by-changing-font is a Windows insanity.
+     */
+    s = ctrl_getset(b, "Window", "size", "Set the size of the window");
+    ctrl_radiobuttons(s, "When window is resized:", 'z', 1,
+                     HELPCTX(window_resize),
+                     dlg_stdradiobutton_handler,
+                     I(offsetof(Config, resize_action)),
+                     "Change the number of rows and columns", I(RESIZE_TERM),
+                     "Change the size of the font", I(RESIZE_FONT),
+                     "Change font size only when maximised", I(RESIZE_EITHER),
+                     "Forbid resizing completely", I(RESIZE_DISABLED), NULL);
+
+    /*
+     * Most of the Window/Behaviour stuff is there to mimic Windows
+     * conventions which PuTTY can optionally disregard. Hence,
+     * most of these options are Windows-specific.
+     */
+    s = ctrl_getset(b, "Window/Behaviour", "main", NULL);
+    ctrl_checkbox(s, "Window closes on ALT-F4", '4',
+                 HELPCTX(behaviour_altf4),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,alt_f4)));
+    ctrl_checkbox(s, "System menu appears on ALT-Space", 'y',
+                 HELPCTX(behaviour_altspace),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,alt_space)));
+    ctrl_checkbox(s, "System menu appears on ALT alone", 'l',
+                 HELPCTX(behaviour_altonly),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,alt_only)));
+    ctrl_checkbox(s, "Ensure window is always on top", 'e',
+                 HELPCTX(behaviour_alwaysontop),
+                 dlg_stdcheckbox_handler, I(offsetof(Config,alwaysontop)));
+    ctrl_checkbox(s, "Full screen on Alt-Enter", 'f',
+                 HELPCTX(behaviour_altenter),
+                 dlg_stdcheckbox_handler,
+                 I(offsetof(Config,fullscreenonaltenter)));
+}
index 5322823..4d055f4 100644 (file)
@@ -3,10 +3,23 @@
  * box.
  */
 
+/*
+ * Possible TODO in new cross-platform config box stuff:
+ *
+ *  - When lining up two controls alongside each other, I wonder if
+ *    we could conveniently arrange to centre them vertically?
+ *    Particularly ugly in the current setup is the `Add new
+ *    forwarded port:' static next to the rather taller `Remove'
+ *    button.
+ */
+
 #include <windows.h>
 #include <commctrl.h>
+#include <assert.h>
 
 #include "winstuff.h"
+#include "misc.h"
+#include "dialog.h"
 #include "puttymem.h"
 
 #include "putty.h"
 #define GAPYBOX 4
 #define DLGWIDTH 168
 #define STATICHEIGHT 8
+#define TITLEHEIGHT 12
 #define CHECKBOXHEIGHT 8
 #define RADIOHEIGHT 8
 #define EDITHEIGHT 12
+#define LISTHEIGHT 11
+#define LISTINCREMENT 8
 #define COMBOHEIGHT 12
 #define PUSHBTNHEIGHT 14
 #define PROGBARHEIGHT 14
@@ -55,10 +71,30 @@ HWND doctl(struct ctlpos *cp, RECT r,
     r.left += cp->xoff;
     MapDialogRect(cp->hwnd, &r);
 
-    ctl = CreateWindowEx(exstyle, wclass, wtext, wstyle,
-                        r.left, r.top, r.right, r.bottom,
-                        cp->hwnd, (HMENU) wid, hinst, NULL);
-    SendMessage(ctl, WM_SETFONT, cp->font, MAKELPARAM(TRUE, 0));
+    /*
+     * We can pass in cp->hwnd == NULL, to indicate a dry run
+     * without creating any actual controls.
+     */
+    if (cp->hwnd) {
+       ctl = CreateWindowEx(exstyle, wclass, wtext, wstyle,
+                            r.left, r.top, r.right, r.bottom,
+                            cp->hwnd, (HMENU) wid, hinst, NULL);
+       SendMessage(ctl, WM_SETFONT, cp->font, MAKELPARAM(TRUE, 0));
+
+       if (!strcmp(wclass, "LISTBOX")) {
+           /*
+            * Bizarre Windows bug: the list box calculates its
+            * number of lines based on the font it has at creation
+            * time, but sending it WM_SETFONT doesn't cause it to
+            * recalculate. So now, _after_ we've sent it
+            * WM_SETFONT, we explicitly resize it (to the same
+            * size it was already!) to force it to reconsider.
+            */
+           SetWindowPos(ctl, NULL, 0, 0, r.right, r.bottom,
+                        SWP_NOACTIVATE | SWP_NOCOPYBITS |
+                        SWP_NOMOVE | SWP_NOZORDER);
+       }
+    }
     return ctl;
 }
 
@@ -116,14 +152,14 @@ void endbox(struct ctlpos *cp)
  * Some edit boxes. Each one has a static above it. The percentages
  * of the horizontal space are provided.
  */
-void multiedit(struct ctlpos *cp, ...)
+void multiedit(struct ctlpos *cp, int password, ...)
 {
     RECT r;
     va_list ap;
     int percent, xpos;
 
     percent = xpos = 0;
-    va_start(ap, cp);
+    va_start(ap, password);
     while (1) {
        char *text;
        int staticid, editid, pcwidth;
@@ -145,7 +181,8 @@ void multiedit(struct ctlpos *cp, ...)
        r.top = cp->ypos + 8 + GAPWITHIN;
        r.bottom = EDITHEIGHT;
        doctl(cp, r, "EDIT",
-             WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
+             WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL |
+             (password ? ES_PASSWORD : 0),
              WS_EX_CLIENTEDGE, "", editid);
     }
     va_end(ap);
@@ -174,29 +211,37 @@ void combobox(struct ctlpos *cp, char *text, int staticid, int listid)
     cp->ypos += STATICHEIGHT + GAPWITHIN + COMBOHEIGHT + GAPBETWEEN;
 }
 
-static void radioline_common(struct ctlpos *cp, int nacross, va_list ap)
+struct radio { char *text; int id; };
+
+static void radioline_common(struct ctlpos *cp, char *text, int id,
+                            int nacross, struct radio *buttons, int nbuttons)
 {
     RECT r;
     int group;
     int i;
-    char *btext;
+    int j;
+
+    if (text) {
+       r.left = GAPBETWEEN;
+       r.top = cp->ypos;
+       r.right = cp->width;
+       r.bottom = STATICHEIGHT;
+       cp->ypos += r.bottom + GAPWITHIN;
+       doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
+    }
 
     group = WS_GROUP;
     i = 0;
-    btext = va_arg(ap, char *);
-    while (1) {
-       char *nextbtext;
-       int bid;
-       if (!btext)
-           break;
+    for (j = 0; j < nbuttons; j++) {
+       char *btext = buttons[j].text;
+       int bid = buttons[j].id;
+
        if (i == nacross) {
-           cp->ypos += r.bottom + GAPBETWEEN;
+           cp->ypos += r.bottom + (nacross > 1 ? GAPBETWEEN : GAPWITHIN);
            i = 0;
        }
-       bid = va_arg(ap, int);
-       nextbtext = va_arg(ap, char *);
        r.left = GAPBETWEEN + i * (cp->width + GAPBETWEEN) / nacross;
-       if (nextbtext)
+       if (j < nbuttons-1)
            r.right =
                (i + 1) * (cp->width + GAPBETWEEN) / nacross - r.left;
        else
@@ -204,11 +249,10 @@ static void radioline_common(struct ctlpos *cp, int nacross, va_list ap)
        r.top = cp->ypos;
        r.bottom = RADIOHEIGHT;
        doctl(cp, r, "BUTTON",
-             BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP |
-             group, 0, btext, bid);
+             BS_NOTIFY | BS_AUTORADIOBUTTON | WS_CHILD |
+             WS_VISIBLE | WS_TABSTOP | group, 0, btext, bid);
        group = 0;
        i++;
-       btext = nextbtext;
     }
     cp->ypos += r.bottom + GAPBETWEEN;
 }
@@ -229,18 +273,29 @@ static void radioline_common(struct ctlpos *cp, int nacross, va_list ap)
  */
 void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...)
 {
-    RECT r;
     va_list ap;
+    struct radio *buttons;
+    int i, nbuttons;
 
-    r.left = GAPBETWEEN;
-    r.top = cp->ypos;
-    r.right = cp->width;
-    r.bottom = STATICHEIGHT;
-    cp->ypos += r.bottom + GAPWITHIN;
-    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
     va_start(ap, nacross);
-    radioline_common(cp, nacross, ap);
+    nbuttons = 0;
+    while (1) {
+       char *btext = va_arg(ap, char *);
+       int bid;
+       if (!btext)
+           break;
+       bid = va_arg(ap, int);
+    }
+    va_end(ap);
+    buttons = smalloc(nbuttons * sizeof(struct radio));
+    va_start(ap, nacross);
+    for (i = 0; i < nbuttons; i++) {
+       buttons[i].text = va_arg(ap, char *);
+       buttons[i].id = va_arg(ap, int);
+    }
     va_end(ap);
+    radioline_common(cp, text, id, nacross, buttons, nbuttons);
+    sfree(buttons);
 }
 
 /*
@@ -250,10 +305,28 @@ void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...)
 void bareradioline(struct ctlpos *cp, int nacross, ...)
 {
     va_list ap;
+    struct radio *buttons;
+    int i, nbuttons;
 
     va_start(ap, nacross);
-    radioline_common(cp, nacross, ap);
+    nbuttons = 0;
+    while (1) {
+       char *btext = va_arg(ap, char *);
+       int bid;
+       if (!btext)
+           break;
+       bid = va_arg(ap, int);
+    }
+    va_end(ap);
+    buttons = smalloc(nbuttons * sizeof(struct radio));
+    va_start(ap, nacross);
+    for (i = 0; i < nbuttons; i++) {
+       buttons[i].text = va_arg(ap, char *);
+       buttons[i].id = va_arg(ap, int);
+    }
     va_end(ap);
+    radioline_common(cp, NULL, 0, nacross, buttons, nbuttons);
+    sfree(buttons);
 }
 
 /*
@@ -262,37 +335,29 @@ void bareradioline(struct ctlpos *cp, int nacross, ...)
  */
 void radiobig(struct ctlpos *cp, char *text, int id, ...)
 {
-    RECT r;
     va_list ap;
-    int group;
+    struct radio *buttons;
+    int i, nbuttons;
 
-    r.left = GAPBETWEEN;
-    r.top = cp->ypos;
-    r.right = cp->width;
-    r.bottom = STATICHEIGHT;
-    cp->ypos += r.bottom + GAPWITHIN;
-    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
     va_start(ap, id);
-    group = WS_GROUP;
+    nbuttons = 0;
     while (1) {
-       char *btext;
+       char *btext = va_arg(ap, char *);
        int bid;
-       btext = va_arg(ap, char *);
        if (!btext)
            break;
        bid = va_arg(ap, int);
-       r.left = GAPBETWEEN;
-       r.top = cp->ypos;
-       r.right = cp->width;
-       r.bottom = STATICHEIGHT;
-       cp->ypos += r.bottom + GAPWITHIN;
-       doctl(cp, r, "BUTTON",
-             BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP |
-             group, 0, btext, bid);
-       group = 0;
     }
     va_end(ap);
-    cp->ypos += GAPBETWEEN - GAPWITHIN;
+    buttons = smalloc(nbuttons * sizeof(struct radio));
+    va_start(ap, id);
+    for (i = 0; i < nbuttons; i++) {
+       buttons[i].text = va_arg(ap, char *);
+       buttons[i].id = va_arg(ap, int);
+    }
+    va_end(ap);
+    radioline_common(cp, text, id, 1, buttons, nbuttons);
+    sfree(buttons);
 }
 
 /*
@@ -308,11 +373,93 @@ void checkbox(struct ctlpos *cp, char *text, int id)
     r.bottom = CHECKBOXHEIGHT;
     cp->ypos += r.bottom + GAPBETWEEN;
     doctl(cp, r, "BUTTON",
-         BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0,
+         BS_NOTIFY | BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0,
          text, id);
 }
 
 /*
+ * Wrap a piece of text for a static text control. Returns the
+ * wrapped text (a malloc'ed string containing \ns), and also
+ * returns the number of lines required.
+ */
+char *staticwrap(struct ctlpos *cp, HWND hwnd, char *text, int *lines)
+{
+    HFONT font = (HFONT) cp->font;
+    HDC hdc = GetDC(hwnd);
+    int lpx = GetDeviceCaps(hdc, LOGPIXELSX);
+    int width, nlines, j;
+    INT *pwidths, nfit;
+    SIZE size;
+    char *ret, *p, *q;
+    RECT r;
+
+    ret = smalloc(1+strlen(text));
+    p = text;
+    q = ret;
+    pwidths = smalloc(sizeof(INT)*(1+strlen(text)));
+
+    /*
+     * Work out the width the text will need to fit in, by doing
+     * the same adjustment that the `statictext' function itself
+     * will perform.
+     * 
+     * We must first convert from dialog-box units into pixels, and
+     * then from pixels into the `logical units' that Windows uses
+     * within GDI. You can't make this stuff up.
+     */
+    r.left = r.top = r.bottom = 0;
+    r.right = cp->width;
+    MapDialogRect(hwnd, &r);
+    width = MulDiv(r.right, lpx, 72);
+
+    nlines = 1;
+
+    while (*p) {
+       if (!GetTextExtentExPoint(hdc, p, strlen(p), width,
+                                 &nfit, pwidths, &size) ||
+           (size_t)nfit >= strlen(p)) {
+           /*
+            * Either GetTextExtentExPoint returned failure, or the
+            * whole of the rest of the text fits on this line.
+            * Either way, we stop wrapping, copy the remainder of
+            * the input string unchanged to the output, and leave.
+            */
+           strcpy(q, p);
+           break;
+       }
+
+       /*
+        * Now we search backwards along the string from `nfit',
+        * looking for a space at which to break the line. If we
+        * don't find one at all, that's fine - we'll just break
+        * the line at `nfit'.
+        */
+       for (j = nfit; j > 0; j--) {
+           if (isspace((unsigned char)p[j])) {
+               nfit = j;
+               break;
+           }
+       }
+
+       strncpy(q, p, nfit);
+       q[nfit] = '\n';
+       q += nfit+1;
+
+       p += nfit;
+       while (*p && isspace((unsigned char)*p))
+           p++;
+
+       nlines++;
+    }
+
+    ReleaseDC(cp->hwnd, hdc);
+
+    if (lines) *lines = nlines;
+
+    return ret;
+}
+
+/*
  * A single standalone static text control.
  */
 void statictext(struct ctlpos *cp, char *text, int lines, int id)
@@ -324,7 +471,25 @@ void statictext(struct ctlpos *cp, char *text, int lines, int id)
     r.right = cp->width;
     r.bottom = STATICHEIGHT * lines;
     cp->ypos += r.bottom + GAPBETWEEN;
-    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
+    doctl(cp, r, "STATIC",
+         WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
+         0, text, id);
+}
+
+/*
+ * An owner-drawn static text control for a panel title.
+ */
+void paneltitle(struct ctlpos *cp, int id)
+{
+    RECT r;
+
+    r.left = GAPBETWEEN;
+    r.top = cp->ypos;
+    r.right = cp->width;
+    r.bottom = TITLEHEIGHT;
+    cp->ypos += r.bottom + GAPBETWEEN;
+    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_OWNERDRAW,
+         0, NULL, id);
 }
 
 /*
@@ -353,13 +518,38 @@ void staticbtn(struct ctlpos *cp, char *stext, int sid,
     r.right = rwid;
     r.bottom = PUSHBTNHEIGHT;
     doctl(cp, r, "BUTTON",
-         WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+         BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
          0, btext, bid);
 
     cp->ypos += height + GAPBETWEEN;
 }
 
 /*
+ * A simple push button.
+ */
+void button(struct ctlpos *cp, char *btext, int bid, int defbtn)
+{
+    RECT r;
+
+    r.left = GAPBETWEEN;
+    r.top = cp->ypos;
+    r.right = cp->width;
+    r.bottom = PUSHBTNHEIGHT;
+
+    /* Q67655: the _dialog box_ must know which button is default
+     * as well as the button itself knowing */
+    if (defbtn && cp->hwnd)
+       SendMessage(cp->hwnd, DM_SETDEFID, bid, 0);
+
+    doctl(cp, r, "BUTTON",
+         BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP |
+         (defbtn ? BS_DEFPUSHBUTTON : 0) | BS_PUSHBUTTON,
+         0, btext, bid);
+
+    cp->ypos += PUSHBTNHEIGHT + GAPBETWEEN;
+}
+
+/*
  * Like staticbtn, but two buttons.
  */
 void static2btn(struct ctlpos *cp, char *stext, int sid,
@@ -387,7 +577,7 @@ void static2btn(struct ctlpos *cp, char *stext, int sid,
     r.right = rwid1;
     r.bottom = PUSHBTNHEIGHT;
     doctl(cp, r, "BUTTON",
-         WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+         BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
          0, btext1, bid1);
 
     r.left = rpos2;
@@ -395,7 +585,7 @@ void static2btn(struct ctlpos *cp, char *stext, int sid,
     r.right = rwid2;
     r.bottom = PUSHBTNHEIGHT;
     doctl(cp, r, "BUTTON",
-         WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+         BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
          0, btext2, bid2);
 
     cp->ypos += height + GAPBETWEEN;
@@ -482,6 +672,64 @@ void staticddl(struct ctlpos *cp, char *stext,
 }
 
 /*
+ * A combo box on the right hand side, with a static to its left.
+ */
+void staticcombo(struct ctlpos *cp, char *stext,
+                int sid, int lid, int percentlist)
+{
+    const int height = (COMBOHEIGHT > STATICHEIGHT ?
+                       COMBOHEIGHT : STATICHEIGHT);
+    RECT r;
+    int lwid, rwid, rpos;
+
+    rpos =
+       GAPBETWEEN + (100 - percentlist) * (cp->width + GAPBETWEEN) / 100;
+    lwid = rpos - 2 * GAPBETWEEN;
+    rwid = cp->width + GAPBETWEEN - rpos;
+
+    r.left = GAPBETWEEN;
+    r.top = cp->ypos + (height - STATICHEIGHT) / 2;
+    r.right = lwid;
+    r.bottom = STATICHEIGHT;
+    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+
+    r.left = rpos;
+    r.top = cp->ypos + (height - EDITHEIGHT) / 2;
+    r.right = rwid;
+    r.bottom = COMBOHEIGHT*10;
+    doctl(cp, r, "COMBOBOX",
+         WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
+         CBS_DROPDOWN | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
+
+    cp->ypos += height + GAPBETWEEN;
+}
+
+/*
+ * A static, with a full-width drop-down list box below it.
+ */
+void staticddlbig(struct ctlpos *cp, char *stext,
+                 int sid, int lid)
+{
+    RECT r;
+
+    r.left = GAPBETWEEN;
+    r.top = cp->ypos;
+    r.right = cp->width;
+    r.bottom = STATICHEIGHT;
+    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+    cp->ypos += STATICHEIGHT;
+
+    r.left = GAPBETWEEN;
+    r.top = cp->ypos;
+    r.right = cp->width;
+    r.bottom = COMBOHEIGHT*4;
+    doctl(cp, r, "COMBOBOX",
+         WS_CHILD | WS_VISIBLE | WS_TABSTOP |
+         CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
+    cp->ypos += COMBOHEIGHT + GAPBETWEEN;
+}
+
+/*
  * A big multiline edit control with a static labelling it.
  */
 void bigeditctrl(struct ctlpos *cp, char *stext,
@@ -507,6 +755,35 @@ void bigeditctrl(struct ctlpos *cp, char *stext,
 }
 
 /*
+ * A list box with a static labelling it.
+ */
+void listbox(struct ctlpos *cp, char *stext,
+            int sid, int lid, int lines, int multi)
+{
+    RECT r;
+
+    if (stext != NULL) {
+       r.left = GAPBETWEEN;
+       r.top = cp->ypos;
+       r.right = cp->width;
+       r.bottom = STATICHEIGHT;
+       cp->ypos += r.bottom + GAPWITHIN;
+       doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+    }
+
+    r.left = GAPBETWEEN;
+    r.top = cp->ypos;
+    r.right = cp->width;
+    r.bottom = LISTHEIGHT + (lines - 1) * LISTINCREMENT;
+    cp->ypos += r.bottom + GAPBETWEEN;
+    doctl(cp, r, "LISTBOX",
+         WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
+         LBS_NOTIFY | LBS_HASSTRINGS | LBS_USETABSTOPS |
+         (multi ? LBS_MULTIPLESEL : 0),
+         WS_EX_CLIENTEDGE, "", lid);
+}
+
+/*
  * A tab-control substitute when a real tab control is unavailable.
  */
 void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id)
@@ -584,320 +861,48 @@ void editbutton(struct ctlpos *cp, char *stext, int sid,
     r.right = rwid;
     r.bottom = PUSHBTNHEIGHT;
     doctl(cp, r, "BUTTON",
-         WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+         BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
          0, btext, bid);
 
     cp->ypos += height + GAPBETWEEN;
 }
 
 /*
- * Special control which was hard to describe generically: the
- * session-saver assembly. A static; below that an edit box; below
- * that a list box. To the right of the list box, a column of
- * buttons.
+ * A special control for manipulating an ordered preference list
+ * (eg. for cipher selection).
+ * XXX: this is a rough hack and could be improved.
  */
-void sesssaver(struct ctlpos *cp, char *text,
-              int staticid, int editid, int listid, ...)
+void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines,
+              char *stext, int sid, int listid, int upbid, int dnbid)
 {
+    const static int percents[] = { 5, 75, 20 };
     RECT r;
-    va_list ap;
-    int lwid, rwid, rpos;
-    int y;
-    const int LISTDEFHEIGHT = 66;
-
-    rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
-    lwid = rpos - 2 * GAPBETWEEN;
-    rwid = cp->width + GAPBETWEEN - rpos;
-
-    /* The static control. */
-    r.left = GAPBETWEEN;
-    r.top = cp->ypos;
-    r.right = lwid;
-    r.bottom = STATICHEIGHT;
-    cp->ypos += r.bottom + GAPWITHIN;
-    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, staticid);
+    int xpos, percent = 0, i;
+    int listheight = LISTHEIGHT + (lines - 1) * LISTINCREMENT;
+    const int BTNSHEIGHT = 2*PUSHBTNHEIGHT + GAPBETWEEN;
+    int totalheight, buttonpos;
 
-    /* The edit control. */
-    r.left = GAPBETWEEN;
-    r.top = cp->ypos;
-    r.right = lwid;
-    r.bottom = EDITHEIGHT;
-    cp->ypos += r.bottom + GAPWITHIN;
-    doctl(cp, r, "EDIT",
-         WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
-         WS_EX_CLIENTEDGE, "", editid);
+    /* Squirrel away IDs. */
+    hdl->listid = listid;
+    hdl->upbid  = upbid;
+    hdl->dnbid  = dnbid;
 
-    /*
-     * The buttons (we should hold off on the list box until we
-     * know how big the buttons are).
-     */
-    va_start(ap, listid);
-    y = cp->ypos;
-    while (1) {
-       char *btext = va_arg(ap, char *);
-       int bid;
-       if (!btext)
-           break;
-       bid = va_arg(ap, int);
-       r.left = rpos;
-       r.top = y;
-       r.right = rwid;
-       r.bottom = PUSHBTNHEIGHT;
-       y += r.bottom + GAPWITHIN;
-       doctl(cp, r, "BUTTON",
-             WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
-             0, btext, bid);
+    /* The static label. */
+    if (stext != NULL) {
+       r.left = GAPBETWEEN;
+       r.top = cp->ypos;
+       r.right = cp->width;
+       r.bottom = STATICHEIGHT;
+       cp->ypos += r.bottom + GAPWITHIN;
+       doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
     }
 
-    /* Compute list box height. LISTDEFHEIGHT, or height of buttons. */
-    y -= cp->ypos;
-    y -= GAPWITHIN;
-    if (y < LISTDEFHEIGHT)
-       y = LISTDEFHEIGHT;
-    r.left = GAPBETWEEN;
-    r.top = cp->ypos;
-    r.right = lwid;
-    r.bottom = y;
-    cp->ypos += y + GAPBETWEEN;
-    doctl(cp, r, "LISTBOX",
-         WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
-         LBS_NOTIFY | LBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", listid);
-}
-
-/*
- * Another special control: the environment-variable setter. A
- * static line first; then a pair of edit boxes with associated
- * statics, and two buttons; then a list box.
- */
-void envsetter(struct ctlpos *cp, char *stext, int sid,
-              char *e1stext, int e1sid, int e1id,
-              char *e2stext, int e2sid, int e2id,
-              int listid, char *b1text, int b1id, char *b2text, int b2id)
-{
-    RECT r;
-    const int height = (STATICHEIGHT > EDITHEIGHT
-                       && STATICHEIGHT >
-                       PUSHBTNHEIGHT ? STATICHEIGHT : EDITHEIGHT >
-                       PUSHBTNHEIGHT ? EDITHEIGHT : PUSHBTNHEIGHT);
-    const static int percents[] = { 20, 35, 10, 25 };
-    int i, j, xpos, percent;
-    const int LISTHEIGHT = 42;
-
-    /* The static control. */
-    r.left = GAPBETWEEN;
-    r.top = cp->ypos;
-    r.right = cp->width;
-    r.bottom = STATICHEIGHT;
-    cp->ypos += r.bottom + GAPWITHIN;
-    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
-
-    /* The statics+edits+buttons. */
-    for (j = 0; j < 2; j++) {
-       percent = 10;
-       for (i = 0; i < 4; i++) {
-           xpos = (cp->width + GAPBETWEEN) * percent / 100;
-           r.left = xpos + GAPBETWEEN;
-           percent += percents[i];
-           xpos = (cp->width + GAPBETWEEN) * percent / 100;
-           r.right = xpos - r.left;
-           r.top = cp->ypos;
-           r.bottom = (i == 0 ? STATICHEIGHT :
-                       i == 1 ? EDITHEIGHT : PUSHBTNHEIGHT);
-           r.top += (height - r.bottom) / 2;
-           if (i == 0) {
-               doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0,
-                     j == 0 ? e1stext : e2stext, j == 0 ? e1sid : e2sid);
-           } else if (i == 1) {
-               doctl(cp, r, "EDIT",
-                     WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
-                     WS_EX_CLIENTEDGE, "", j == 0 ? e1id : e2id);
-           } else if (i == 3) {
-               doctl(cp, r, "BUTTON",
-                     WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
-                     0, j == 0 ? b1text : b2text, j == 0 ? b1id : b2id);
-           }
-       }
-       cp->ypos += height + GAPWITHIN;
-    }
-
-    /* The list box. */
-    r.left = GAPBETWEEN;
-    r.top = cp->ypos;
-    r.right = cp->width;
-    r.bottom = LISTHEIGHT;
-    cp->ypos += r.bottom + GAPBETWEEN;
-    doctl(cp, r, "LISTBOX",
-         WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | LBS_HASSTRINGS
-         | LBS_USETABSTOPS, WS_EX_CLIENTEDGE, "", listid);
-}
-
-/*
- * Yet another special control: the character-class setter. A
- * static, then a list, then a line containing a
- * button-and-static-and-edit. 
- */
-void charclass(struct ctlpos *cp, char *stext, int sid, int listid,
-              char *btext, int bid, int eid, char *s2text, int s2id)
-{
-    RECT r;
-    const int height = (STATICHEIGHT > EDITHEIGHT
-                       && STATICHEIGHT >
-                       PUSHBTNHEIGHT ? STATICHEIGHT : EDITHEIGHT >
-                       PUSHBTNHEIGHT ? EDITHEIGHT : PUSHBTNHEIGHT);
-    const static int percents[] = { 30, 40, 30 };
-    int i, xpos, percent;
-    const int LISTHEIGHT = 52;
-
-    /* The static control. */
-    r.left = GAPBETWEEN;
-    r.top = cp->ypos;
-    r.right = cp->width;
-    r.bottom = STATICHEIGHT;
-    cp->ypos += r.bottom + GAPWITHIN;
-    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
-
-    /* The list box. */
-    r.left = GAPBETWEEN;
-    r.top = cp->ypos;
-    r.right = cp->width;
-    r.bottom = LISTHEIGHT;
-    cp->ypos += r.bottom + GAPWITHIN;
-    doctl(cp, r, "LISTBOX",
-         WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | LBS_HASSTRINGS
-         | LBS_USETABSTOPS, WS_EX_CLIENTEDGE, "", listid);
-
-    /* The button+static+edit. */
-    percent = xpos = 0;
-    for (i = 0; i < 3; i++) {
-       r.left = xpos + GAPBETWEEN;
-       percent += percents[i];
-       xpos = (cp->width + GAPBETWEEN) * percent / 100;
-       r.right = xpos - r.left;
-       r.top = cp->ypos;
-       r.bottom = (i == 0 ? PUSHBTNHEIGHT :
-                   i == 1 ? STATICHEIGHT : EDITHEIGHT);
-       r.top += (height - r.bottom) / 2;
-       if (i == 0) {
-           doctl(cp, r, "BUTTON",
-                 WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
-                 0, btext, bid);
-       } else if (i == 1) {
-           doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_CENTER,
-                 0, s2text, s2id);
-       } else if (i == 2) {
-           doctl(cp, r, "EDIT",
-                 WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
-                 WS_EX_CLIENTEDGE, "", eid);
-       }
-    }
-    cp->ypos += height + GAPBETWEEN;
-}
-
-/*
- * A special control (horrors!). The colour editor. A static line;
- * then on the left, a list box, and on the right, a sequence of
- * two-part statics followed by a button.
- */
-void colouredit(struct ctlpos *cp, char *stext, int sid, int listid,
-               char *btext, int bid, ...)
-{
-    RECT r;
-    int y;
-    va_list ap;
-    int lwid, rwid, rpos;
-    const int LISTHEIGHT = 66;
-
-    /* The static control. */
-    r.left = GAPBETWEEN;
-    r.top = cp->ypos;
-    r.right = cp->width;
-    r.bottom = STATICHEIGHT;
-    cp->ypos += r.bottom + GAPWITHIN;
-    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
-
-    rpos = GAPBETWEEN + 2 * (cp->width + GAPBETWEEN) / 3;
-    lwid = rpos - 2 * GAPBETWEEN;
-    rwid = cp->width + GAPBETWEEN - rpos;
-
-    /* The list box. */
-    r.left = GAPBETWEEN;
-    r.top = cp->ypos;
-    r.right = lwid;
-    r.bottom = LISTHEIGHT;
-    doctl(cp, r, "LISTBOX",
-         WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | LBS_HASSTRINGS
-         | LBS_USETABSTOPS | LBS_NOTIFY, WS_EX_CLIENTEDGE, "", listid);
-
-    /* The statics. */
-    y = cp->ypos;
-    va_start(ap, bid);
-    while (1) {
-       char *ltext;
-       int lid, rid;
-       ltext = va_arg(ap, char *);
-       if (!ltext)
-           break;
-       lid = va_arg(ap, int);
-       rid = va_arg(ap, int);
-       r.top = y;
-       r.bottom = STATICHEIGHT;
-       y += r.bottom + GAPWITHIN;
-       r.left = rpos;
-       r.right = rwid / 2;
-       doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, ltext, lid);
-       r.left = rpos + r.right;
-       r.right = rwid - r.right;
-       doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_RIGHT, 0, "",
-             rid);
-    }
-    va_end(ap);
-
-    /* The button. */
-    r.top = y + 2 * GAPWITHIN;
-    r.bottom = PUSHBTNHEIGHT;
-    r.left = rpos;
-    r.right = rwid;
-    doctl(cp, r, "BUTTON",
-         WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
-         0, btext, bid);
-
-    cp->ypos += LISTHEIGHT + GAPBETWEEN;
-}
-
-/*
- * A special control for manipulating an ordered preference list
- * (eg. for cipher selection).
- * XXX: this is a rough hack and could be improved.
- */
-void prefslist(struct prefslist *hdl, struct ctlpos *cp, char *stext,
-               int sid, int listid, int upbid, int dnbid)
-{
-    const static int percents[] = { 5, 75, 20 };
-    RECT r;
-    int xpos, percent = 0, i;
-    const int DEFLISTHEIGHT = 52;      /* XXX configurable? */
-    const int BTNSHEIGHT = 2*PUSHBTNHEIGHT + GAPBETWEEN;
-    int totalheight;
-
-    /* Squirrel away IDs. */
-    hdl->listid = listid;
-    hdl->upbid  = upbid;
-    hdl->dnbid  = dnbid;
-
-    /* The static label. */
-    r.left = GAPBETWEEN;
-    r.top = cp->ypos;
-    r.right = cp->width;
-    r.bottom = STATICHEIGHT;
-    cp->ypos += r.bottom + GAPWITHIN;
-    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
-
-    /* XXX it'd be nice to centre the buttons wrt the listbox
-     * but we'd have to find out how high the latter actually is. */
-    if (DEFLISTHEIGHT > BTNSHEIGHT) {
-        totalheight = DEFLISTHEIGHT;
-    } else {
-        totalheight = BTNSHEIGHT;
+    if (listheight > BTNSHEIGHT) {
+        totalheight = listheight;
+       buttonpos = (listheight - BTNSHEIGHT) / 2;
+    } else {
+        totalheight = BTNSHEIGHT;
+       buttonpos = 0;
     }
 
     for (i=0; i<3; i++) {
@@ -912,12 +917,12 @@ void prefslist(struct prefslist *hdl, struct ctlpos *cp, char *stext,
           case 1:
             /* The drag list box. */
             r.left = left; r.right = wid;
-            r.top = cp->ypos; r.bottom = totalheight;
+            r.top = cp->ypos; r.bottom = listheight;
             {
                 HWND ctl;
                 ctl = doctl(cp, r, "LISTBOX",
                             WS_CHILD | WS_VISIBLE | WS_TABSTOP |
-                           WS_VSCROLL | LBS_HASSTRINGS,
+                           WS_VSCROLL | LBS_HASSTRINGS | LBS_USETABSTOPS,
                             WS_EX_CLIENTEDGE,
                             "", listid);
                MakeDragList(ctl);
@@ -929,16 +934,18 @@ void prefslist(struct prefslist *hdl, struct ctlpos *cp, char *stext,
            /* XXX worry about accelerators if we have more than one
             * prefslist on a panel */
             r.left = left; r.right = wid;
-            r.top = cp->ypos; r.bottom = PUSHBTNHEIGHT;
+            r.top = cp->ypos + buttonpos; r.bottom = PUSHBTNHEIGHT;
             doctl(cp, r, "BUTTON",
-                  WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+                  BS_NOTIFY | WS_CHILD | WS_VISIBLE |
+                 WS_TABSTOP | BS_PUSHBUTTON,
                   0, "&Up", upbid);
 
             r.left = left; r.right = wid;
-            r.top = cp->ypos + PUSHBTNHEIGHT + GAPBETWEEN;
+            r.top = cp->ypos + buttonpos + PUSHBTNHEIGHT + GAPBETWEEN;
             r.bottom = PUSHBTNHEIGHT;
             doctl(cp, r, "BUTTON",
-                  WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+                  BS_NOTIFY | WS_CHILD | WS_VISIBLE |
+                 WS_TABSTOP | BS_PUSHBUTTON,
                   0, "&Down", dnbid);
 
             break;
@@ -1016,6 +1023,10 @@ int pl_itemfrompt(HWND hwnd, POINT cursor, BOOL scroll)
 
 /*
  * Handler for prefslist above.
+ * 
+ * Return value has bit 0 set if the dialog box procedure needs to
+ * return TRUE from handling this message; it has bit 1 set if a
+ * change may have been made in the contents of the list.
  */
 int handle_prefslist(struct prefslist *hdl,
                      int *array, int maxmemb,
@@ -1023,7 +1034,7 @@ int handle_prefslist(struct prefslist *hdl,
                     WPARAM wParam, LPARAM lParam)
 {
     int i;
-    int ret;
+    int ret = 0;
 
     if (is_dlmsg) {
 
@@ -1040,13 +1051,13 @@ int handle_prefslist(struct prefslist *hdl,
                hdl->dragging = 0;
                /* XXX hack Q183115 */
                SetWindowLong(hwnd, DWL_MSGRESULT, TRUE);
-                ret = 1; break;
+                ret |= 1; break;
               case DL_CANCELDRAG:
                DrawInsert(hwnd, dlm->hWnd, -1);     /* Clear arrow */
                SendDlgItemMessage(hwnd, hdl->listid,
                                   LB_DELETESTRING, hdl->dummyitem, 0);
                hdl->dragging = 0;
-                ret = 1; break;
+                ret |= 1; break;
               case DL_DRAGGING:
                hdl->dragging = 1;
                dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, TRUE);
@@ -1056,7 +1067,7 @@ int handle_prefslist(struct prefslist *hdl,
                    SetWindowLong(hwnd, DWL_MSGRESULT, DL_MOVECURSOR);
                else
                    SetWindowLong(hwnd, DWL_MSGRESULT, DL_STOPCURSOR);
-                ret = 1; break;
+                ret |= 1; break;
               case DL_DROPPED:
                if (hdl->dragging) {
                    dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, TRUE);
@@ -1072,14 +1083,14 @@ int handle_prefslist(struct prefslist *hdl,
                        if (dest > hdl->srcitem) dest--;
                        pl_moveitem(hwnd, hdl->listid, hdl->srcitem, dest);
                    }
+                   ret |= 2;
                }
-                ret = 1; break;
+                ret |= 1; break;
             }
         }
 
     } else {
 
-        ret = 0;
         if (((LOWORD(wParam) == hdl->upbid) ||
              (LOWORD(wParam) == hdl->dnbid)) &&
             ((HIWORD(wParam) == BN_CLICKED) ||
@@ -1098,19 +1109,21 @@ int handle_prefslist(struct prefslist *hdl,
                    pl_moveitem(hwnd, hdl->listid, selection, selection - 1);
                else if (LOWORD(wParam) == hdl->dnbid && (selection < nitems - 1))
                    pl_moveitem(hwnd, hdl->listid, selection, selection + 1);
+               ret |= 2;
             }
 
         }
 
     }
 
-    /* Update array to match the list box. */
-    for (i=0; i < maxmemb; i++)
-        array[i] = SendDlgItemMessage (hwnd, hdl->listid, LB_GETITEMDATA,
-                                       i, 0);
+    if (array) {
+       /* Update array to match the list box. */
+       for (i=0; i < maxmemb; i++)
+           array[i] = SendDlgItemMessage (hwnd, hdl->listid, LB_GETITEMDATA,
+                                          i, 0);
+    }
 
     return ret;
-
 }
 
 /*
@@ -1135,82 +1148,1267 @@ void progressbar(struct ctlpos *cp, int id)
          , WS_EX_CLIENTEDGE, "", id);
 }
 
+/* ----------------------------------------------------------------------
+ * Platform-specific side of portable dialog-box mechanism.
+ */
+
 /*
- * Another special control: the forwarding options setter. First a
- * list box; next a static header line, introducing a pair of edit
- * boxes with associated statics, another button, and a radio
- * button pair. Then we have a bareradioline, which is included in
- * this control group because it belongs before the `Add' button in
- * the tab order.
+ * This function takes a string, escapes all the ampersands, and
+ * places a single (unescaped) ampersand in front of the first
+ * occurrence of the given shortcut character (which may be
+ * NO_SHORTCUT).
+ * 
+ * Return value is a malloc'ed copy of the processed version of the
+ * string.
  */
-void fwdsetter(struct ctlpos *cp, int listid, char *stext, int sid,
-              char *e1stext, int e1sid, int e1id,
-              char *e2stext, int e2sid, int e2id,
-              char *btext, int bid,
-              char *r1text, int r1id, char *r2text, int r2id)
-{
-    RECT r, button_r;
-    const int height = (STATICHEIGHT > EDITHEIGHT
-                       && STATICHEIGHT >
-                       PUSHBTNHEIGHT ? STATICHEIGHT : EDITHEIGHT >
-                       PUSHBTNHEIGHT ? EDITHEIGHT : PUSHBTNHEIGHT);
-    const static int percents[] = { 25, 35, 15, 25 };
-    int i, j, xpos, percent;
-    const int LISTHEIGHT = 42;
-
-    /* The list box. */
-    r.left = GAPBETWEEN;
-    r.top = cp->ypos;
-    r.right = cp->width;
-    r.bottom = LISTHEIGHT;
-    cp->ypos += r.bottom + GAPBETWEEN;
-    doctl(cp, r, "LISTBOX",
-         WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | LBS_HASSTRINGS
-         | LBS_USETABSTOPS, WS_EX_CLIENTEDGE, "", listid);
+static char *shortcut_escape(char *text, char shortcut)
+{
+    char *ret;
+    char *p, *q;
+
+    if (!text)
+       return NULL;                   /* sfree won't choke on this */
+
+    ret = smalloc(2*strlen(text)+1);   /* size potentially doubles! */
+    shortcut = tolower((unsigned char)shortcut);
+
+    p = text;
+    q = ret;
+    while (*p) {
+       if (shortcut != NO_SHORTCUT &&
+           tolower((unsigned char)*p) == shortcut) {
+           *q++ = '&';
+           shortcut = NO_SHORTCUT;    /* stop it happening twice */
+       } else if (*p == '&') {
+           *q++ = '&';
+       }
+       *q++ = *p++;
+    }
+    *q = '\0';
+    return ret;
+}
 
-    /* The static control. */
-    r.left = GAPBETWEEN;
-    r.top = cp->ypos;
-    r.right = cp->width;
-    r.bottom = STATICHEIGHT;
-    cp->ypos += r.bottom + GAPWITHIN;
-    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
+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);
+    }
 
-    /* The statics+edits+buttons. */
-    for (j = 0; j < 2; j++) {
-       percent = 0;
-       for (i = 0; i < (j ? 2 : 4); i++) {
-           xpos = (cp->width + GAPBETWEEN) * percent / 100;
-           r.left = xpos + GAPBETWEEN;
-           percent += percents[i];
-           if (j==1 && i==1) percent = 100;
-           xpos = (cp->width + GAPBETWEEN) * percent / 100;
-           r.right = xpos - r.left;
-           r.top = cp->ypos;
-           r.bottom = (i == 0 ? STATICHEIGHT :
-                       i == 1 ? EDITHEIGHT : PUSHBTNHEIGHT);
-           r.top += (height - r.bottom) / 2;
-           if (i == 0) {
-               doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0,
-                     j == 0 ? e1stext : e2stext, j == 0 ? e1sid : e2sid);
-           } else if (i == 1) {
-               doctl(cp, r, "EDIT",
-                     WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
-                     WS_EX_CLIENTEDGE, "", j == 0 ? e1id : e2id);
-           } else if (i == 3) {
+    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], nshortcuts;
+    char *escaped;
+    int i, base_id, num_ids, orig_tabdelay;
+    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];
+
+       orig_tabdelay = FALSE;
+
+       /*
+        * 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 postpone creation of the button until we've
-                * done everything else, since it belongs last in
-                * the tab order.
+                * We're recombining the various columns into one.
                 */
-               button_r = r;          /* structure copy */
+               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;
+           orig_tabdelay = TRUE;
+
+           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 */
+
+       } 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;
            }
        }
-       cp->ypos += height + GAPWITHIN;
+
+       /* 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);
+           }
+           if (ctrl->listbox.ncols) {
+               /*
+                * This method of getting the box width is a bit of
+                * a hack; we'd do better to try to retrieve the
+                * actual width in dialog units from doctl() just
+                * before MapDialogRect. But that's going to be no
+                * fun, and this should be good enough accuracy.
+                */
+               int width = cp->width * ctrl->listbox.percentwidth;
+               int *tabarray;
+               int i, percent;
+
+               tabarray = smalloc((ctrl->listbox.ncols-1) * sizeof(int));
+               percent = 0;
+               for (i = 0; i < ctrl->listbox.ncols-1; i++) {
+                   percent += ctrl->listbox.percentages[i];
+                   tabarray[i] = width * percent / 10000;
+               }
+               SendDlgItemMessage(cp->hwnd, base_id+1, LB_SETTABSTOPS,
+                                  ctrl->listbox.ncols-1, (LPARAM)tabarray);
+               sfree(tabarray);
+           }
+           sfree(escaped);
+           break;
+         case CTRL_FILESELECT:
+           num_ids = 3;
+           escaped = shortcut_escape(ctrl->fileselect.label,
+                                     ctrl->fileselect.shortcut);
+           shortcuts[nshortcuts++] = ctrl->fileselect.shortcut;
+           editbutton(&pos, escaped, base_id, base_id+1,
+                      "Bro&wse...", base_id+2);
+           shortcuts[nshortcuts++] = 'w';
+           sfree(escaped);
+           break;
+         case CTRL_FONTSELECT:
+           num_ids = 3;
+           escaped = shortcut_escape(ctrl->fontselect.label,
+                                     ctrl->fontselect.shortcut);
+           shortcuts[nshortcuts++] = ctrl->fontselect.shortcut;
+           statictext(&pos, escaped, 1, base_id);
+           staticbtn(&pos, "", base_id+1, "Change...", base_id+2);
+           sfree(escaped);
+           data = smalloc(sizeof(FontSpec));
+           break;
+         default:
+           assert(!"Can't happen");
+           break;
+       }
+
+       /*
+        * Create a `struct winctrl' for this control, and advance
+        * the dialog ID counter, if it's actually been created
+        * (and isn't tabdelayed).
+        */
+       if (pos.hwnd) {
+           struct winctrl *c = smalloc(sizeof(struct winctrl));
+
+           c->ctrl = ctrl;
+           c->base_id = base_id;
+           c->num_ids = num_ids;
+           c->data = data;
+           memcpy(c->shortcuts, shortcuts, sizeof(shortcuts));
+           winctrl_add(wc, c);
+           winctrl_add_shortcuts(dp, c);
+           base_id += num_ids;
+       }
+
+       if (!orig_tabdelay) {
+           /*
+            * Update the ypos in all columns crossed by this
+            * control.
+            */
+           int i;
+           for (i = colstart; i < colstart+colspan; i++)
+               columns[i].ypos = pos.ypos;
+       }
     }
-    bareradioline(cp, 2, r1text, r1id, r2text, r2id, NULL);
-    /* Create the postponed button. */
-    doctl(cp, button_r, "BUTTON",
-         WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
-         0, btext, bid);
+
+    /*
+     * We've now finished laying out the controls; so now update
+     * the ctlpos and control ID that were passed in, terminate
+     * any containing box, and return.
+     */
+    for (i = 0; i < ncols; i++)
+       if (cp->ypos < columns[i].ypos)
+           cp->ypos = columns[i].ypos;
+    *id = base_id;
+
+    if (s->boxname && *s->boxname)
+       endbox(cp);
+}
+
+static void winctrl_set_focus(union control *ctrl, struct dlgparam *dp,
+                             int has_focus)
+{
+    if (has_focus) {
+       if (dp->focused)
+           dp->lastfocused = dp->focused;
+       dp->focused = ctrl;
+    } else if (!has_focus && dp->focused == ctrl) {
+       dp->lastfocused = dp->focused;
+       dp->focused = NULL;
+    }
+}
+
+union control *dlg_last_focused(void *dlg)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    return dp->lastfocused;
+}
+
+/*
+ * The dialog-box procedure calls this function to handle Windows
+ * messages on a control we manage.
+ */
+int winctrl_handle_command(struct dlgparam *dp, UINT msg,
+                          WPARAM wParam, LPARAM lParam)
+{
+    struct winctrl *c;
+    union control *ctrl;
+    int i, id, ret;
+    static UINT draglistmsg = WM_NULL;
+
+    /*
+     * Filter out pointless window messages. Our interest is in
+     * WM_COMMAND and the drag list message, and nothing else.
+     */
+    if (draglistmsg == WM_NULL)
+       draglistmsg = RegisterWindowMessage (DRAGLISTMSGSTRING);
+
+    if (msg != draglistmsg && msg != WM_COMMAND && msg != WM_DRAWITEM)
+       return 0;
+
+    /*
+     * Look up the control ID in our data.
+     */
+    for (i = 0; i < dp->nctrltrees; i++) {
+       c = winctrl_findbyid(dp->controltrees[i], LOWORD(wParam));
+       if (c)
+           break;
+    }
+    if (!c)
+       return 0;                      /* we have nothing to do */
+
+    if (msg == WM_DRAWITEM) {
+       /*
+        * Owner-draw request for a panel title.
+        */
+       LPDRAWITEMSTRUCT di = (LPDRAWITEMSTRUCT) lParam;
+       HDC hdc = di->hDC;
+       RECT r = di->rcItem;
+       SIZE s;
+
+       GetTextExtentPoint32(hdc, (char *)c->data,
+                                strlen((char *)c->data), &s);
+       DrawEdge(hdc, &r, EDGE_ETCHED, BF_ADJUST | BF_RECT);
+       TextOut(hdc,
+               r.left + (r.right-r.left-s.cx)/2,
+               r.top + (r.bottom-r.top-s.cy)/2,
+               (char *)c->data, strlen((char *)c->data));
+
+       return TRUE;
+    }
+
+    ctrl = c->ctrl;
+    id = LOWORD(wParam) - c->base_id;
+
+    if (!ctrl || !ctrl->generic.handler)
+       return 0;                      /* nothing we can do here */
+
+    /*
+     * From here on we do not issue `return' statements until the
+     * very end of the dialog box: any event handler is entitled to
+     * ask for a colour selector, so we _must_ always allow control
+     * to reach the end of this switch statement so that the
+     * subsequent code can test dp->coloursel_wanted().
+     */
+    ret = 0;
+    dp->coloursel_wanted = FALSE;
+
+    /*
+     * Now switch on the control type and the message.
+     */
+    switch (ctrl->generic.type) {
+      case CTRL_EDITBOX:
+       if (msg == WM_COMMAND && !ctrl->editbox.has_list &&
+           (HIWORD(wParam) == EN_SETFOCUS || HIWORD(wParam) == EN_KILLFOCUS))
+           winctrl_set_focus(ctrl, dp, HIWORD(wParam) == EN_SETFOCUS);
+       if (msg == WM_COMMAND && ctrl->editbox.has_list &&
+           (HIWORD(wParam)==CBN_SETFOCUS || HIWORD(wParam)==CBN_KILLFOCUS))
+           winctrl_set_focus(ctrl, dp, HIWORD(wParam) == CBN_SETFOCUS);
+
+       if (msg == WM_COMMAND && !ctrl->editbox.has_list &&
+           HIWORD(wParam) == EN_CHANGE)
+           ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+       if (msg == WM_COMMAND &&
+           ctrl->editbox.has_list) {
+           if (HIWORD(wParam) == CBN_SELCHANGE) {
+               int index, len;
+               char *text;
+
+               index = SendDlgItemMessage(dp->hwnd, c->base_id+1,
+                                          CB_GETCURSEL, 0, 0);
+               len = SendDlgItemMessage(dp->hwnd, c->base_id+1,
+                                        CB_GETLBTEXTLEN, index, 0);
+               text = smalloc(len+1);
+               SendDlgItemMessage(dp->hwnd, c->base_id+1, CB_GETLBTEXT,
+                                  index, (LPARAM)text);
+               SetDlgItemText(dp->hwnd, c->base_id+1, text);
+               sfree(text);
+               ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+           } else if (HIWORD(wParam) == CBN_EDITCHANGE) {
+               ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+           } else if (HIWORD(wParam) == CBN_KILLFOCUS) {
+               ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH);
+           }
+
+       }
+       break;
+      case CTRL_RADIO:
+       if (msg == WM_COMMAND &&
+           (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+           winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+       /*
+        * We sometimes get spurious BN_CLICKED messages for the
+        * radio button that is just about to _lose_ selection, if
+        * we're switching using the arrow keys. Therefore we
+        * double-check that the button in wParam is actually
+        * checked before generating an event.
+        */
+       if (msg == WM_COMMAND &&
+           HIWORD(wParam) == BN_CLICKED ||
+           HIWORD(wParam) == BN_DOUBLECLICKED &&
+           IsDlgButtonChecked(dp->hwnd, LOWORD(wParam))) {
+           ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+       }
+       break;
+      case CTRL_CHECKBOX:
+       if (msg == WM_COMMAND &&
+           (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+           winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+       if (msg == WM_COMMAND &&
+           (HIWORD(wParam) == BN_CLICKED ||
+            HIWORD(wParam) == BN_DOUBLECLICKED)) {
+           ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+       }
+       break;
+      case CTRL_BUTTON:
+       if (msg == WM_COMMAND &&
+           (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+           winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+       if (msg == WM_COMMAND &&
+           (HIWORD(wParam) == BN_CLICKED ||
+            HIWORD(wParam) == BN_DOUBLECLICKED)) {
+           ctrl->generic.handler(ctrl, dp, dp->data, EVENT_ACTION);
+       }
+       break;
+      case CTRL_LISTBOX:
+       if (msg == WM_COMMAND && ctrl->listbox.height != 0 &&
+           (HIWORD(wParam)==LBN_SETFOCUS || HIWORD(wParam)==LBN_KILLFOCUS))
+           winctrl_set_focus(ctrl, dp, HIWORD(wParam) == LBN_SETFOCUS);
+       if (msg == WM_COMMAND && ctrl->listbox.height == 0 &&
+           (HIWORD(wParam)==CBN_SETFOCUS || HIWORD(wParam)==CBN_KILLFOCUS))
+           winctrl_set_focus(ctrl, dp, HIWORD(wParam) == CBN_SETFOCUS);
+       if (msg == WM_COMMAND && id >= 2 &&
+           (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+           winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+       if (ctrl->listbox.draglist) {
+           int pret;
+           pret = handle_prefslist(c->data, NULL, 0, (msg != WM_COMMAND),
+                                   dp->hwnd, wParam, lParam);
+           if (pret & 2)
+               ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+           ret = pret & 1;
+       } else {
+           if (msg == WM_COMMAND && HIWORD(wParam) == LBN_DBLCLK) {
+               SetCapture(dp->hwnd);
+               ctrl->generic.handler(ctrl, dp, dp->data, EVENT_ACTION);
+           } else if (msg == WM_COMMAND && HIWORD(wParam) == LBN_SELCHANGE) {
+               ctrl->generic.handler(ctrl, dp, dp->data, EVENT_SELCHANGE);
+           }
+       }
+       break;
+      case CTRL_FILESELECT:
+       if (msg == WM_COMMAND && id == 1 &&
+           (HIWORD(wParam) == EN_SETFOCUS || HIWORD(wParam) == EN_KILLFOCUS))
+           winctrl_set_focus(ctrl, dp, HIWORD(wParam) == EN_SETFOCUS);
+       if (msg == WM_COMMAND && id == 2 &&
+           (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+           winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+       if (id == 2 &&
+           (msg == WM_COMMAND &&
+            (HIWORD(wParam) == BN_CLICKED ||
+             HIWORD(wParam) == BN_DOUBLECLICKED))) {
+           OPENFILENAME of;
+           char filename[FILENAME_MAX];
+           int ret;
+
+           memset(&of, 0, sizeof(of));
+#ifdef OPENFILENAME_SIZE_VERSION_400
+           of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
+#else
+           of.lStructSize = sizeof(of);
+#endif
+           of.hwndOwner = dp->hwnd;
+           if (ctrl->fileselect.filter)
+               of.lpstrFilter = ctrl->fileselect.filter;
+           else
+               of.lpstrFilter = "All Files (*.*)\0*\0\0\0";
+           of.lpstrCustomFilter = NULL;
+           of.nFilterIndex = 1;
+           of.lpstrFile = filename;
+           GetDlgItemText(dp->hwnd, c->base_id+1, filename, lenof(filename));
+           filename[lenof(filename)-1] = '\0';
+           of.nMaxFile = lenof(filename);
+           of.lpstrFileTitle = NULL;
+           of.lpstrInitialDir = NULL;
+           of.lpstrTitle = ctrl->fileselect.title;
+           of.Flags = 0;
+           if (ctrl->fileselect.for_writing)
+               ret = GetSaveFileName(&of);
+           else
+               ret = GetOpenFileName(&of);
+           if (ret) {
+               SetDlgItemText(dp->hwnd, c->base_id + 1, filename);
+               ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+           }
+       }
+       break;
+      case CTRL_FONTSELECT:
+       if (msg == WM_COMMAND && id == 2 &&
+           (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
+           winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
+       if (id == 2 &&
+           (msg == WM_COMMAND &&
+            (HIWORD(wParam) == BN_CLICKED ||
+             HIWORD(wParam) == BN_DOUBLECLICKED))) {
+           CHOOSEFONT cf;
+           LOGFONT lf;
+           HDC hdc;
+           FontSpec fs = *(FontSpec *)c->data;
+           
+           hdc = GetDC(0);
+           lf.lfHeight = -MulDiv(fs.height,
+                                 GetDeviceCaps(hdc, LOGPIXELSY), 72);
+           ReleaseDC(0, hdc);
+           lf.lfWidth = lf.lfEscapement = lf.lfOrientation = 0;
+           lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = 0;
+           lf.lfWeight = (fs.isbold ? FW_BOLD : 0);
+           lf.lfCharSet = fs.charset;
+           lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
+           lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
+           lf.lfQuality = DEFAULT_QUALITY;
+           lf.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
+           strncpy(lf.lfFaceName, fs.name,
+                   sizeof(lf.lfFaceName) - 1);
+           lf.lfFaceName[sizeof(lf.lfFaceName) - 1] = '\0';
+
+           cf.lStructSize = sizeof(cf);
+           cf.hwndOwner = dp->hwnd;
+           cf.lpLogFont = &lf;
+           cf.Flags = CF_FIXEDPITCHONLY | CF_FORCEFONTEXIST |
+               CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
+
+           if (ChooseFont(&cf)) {
+               strncpy(fs.name, lf.lfFaceName,
+                       sizeof(fs.name) - 1);
+               fs.name[sizeof(fs.name) - 1] = '\0';
+               fs.isbold = (lf.lfWeight == FW_BOLD);
+               fs.charset = lf.lfCharSet;
+               fs.height = cf.iPointSize / 10;
+               dlg_fontsel_set(ctrl, dp, fs);
+               ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
+           }
+       }
+       break;
+    }
+
+    /*
+     * If the above event handler has asked for a colour selector,
+     * now is the time to generate one.
+     */
+    if (dp->coloursel_wanted) {
+       static CHOOSECOLOR cc;
+       static DWORD custom[16] = { 0 };    /* zero initialisers */
+       cc.lStructSize = sizeof(cc);
+       cc.hwndOwner = dp->hwnd;
+       cc.hInstance = (HWND) hinst;
+       cc.lpCustColors = custom;
+       cc.rgbResult = RGB(dp->coloursel_result.r,
+                          dp->coloursel_result.g,
+                          dp->coloursel_result.b);
+       cc.Flags = CC_FULLOPEN | CC_RGBINIT;
+       if (ChooseColor(&cc)) {
+           dp->coloursel_result.r =
+               (unsigned char) (cc.rgbResult & 0xFF);
+           dp->coloursel_result.g =
+               (unsigned char) (cc.rgbResult >> 8) & 0xFF;
+           dp->coloursel_result.b =
+               (unsigned char) (cc.rgbResult >> 16) & 0xFF;
+           dp->coloursel_result.ok = TRUE;
+       } else
+           dp->coloursel_result.ok = FALSE;
+       ctrl->generic.handler(ctrl, dp, dp->data, EVENT_CALLBACK);
+    }
+
+    return ret;
+}
+
+/*
+ * This function can be called to produce context help on a
+ * control. Returns TRUE if it has actually launched WinHelp.
+ */
+int winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id)
+{
+    int i;
+    struct winctrl *c;
+    char *cmd;
+
+    /*
+     * Look up the control ID in our data.
+     */
+    for (i = 0; i < dp->nctrltrees; i++) {
+       c = winctrl_findbyid(dp->controltrees[i], id);
+       if (c)
+           break;
+    }
+    if (!c)
+       return 0;                      /* we have nothing to do */
+
+    /*
+     * This is the Windows front end, so we're allowed to assume
+     * `helpctx.p' is a context string.
+     */
+    if (!c->ctrl || !c->ctrl->generic.helpctx.p)
+       return 0;                      /* no help available for this ctrl */
+
+    cmd = dupprintf("JI(`',`%s')", c->ctrl->generic.helpctx.p);
+    WinHelp(hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
+    sfree(cmd);
+    return 1;
+}
+
+/*
+ * Now the various functions that the platform-independent
+ * mechanism can call to access the dialog box entries.
+ */
+
+static struct winctrl *dlg_findbyctrl(struct dlgparam *dp, union control *ctrl)
+{
+    int i;
+
+    for (i = 0; i < dp->nctrltrees; i++) {
+       struct winctrl *c = winctrl_findbyctrl(dp->controltrees[i], ctrl);
+       if (c)
+           return c;
+    }
+    return NULL;
+}
+
+void dlg_radiobutton_set(union control *ctrl, void *dlg, int whichbutton)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    assert(c && c->ctrl->generic.type == CTRL_RADIO);
+    CheckRadioButton(dp->hwnd,
+                    c->base_id + 1,
+                    c->base_id + c->ctrl->radio.nbuttons,
+                    c->base_id + 1 + whichbutton);
+}
+
+int dlg_radiobutton_get(union control *ctrl, void *dlg)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    int i;
+    assert(c && c->ctrl->generic.type == CTRL_RADIO);
+    for (i = 0; i < c->ctrl->radio.nbuttons; i++)
+       if (IsDlgButtonChecked(dp->hwnd, c->base_id + 1 + i))
+           return i;
+    assert(!"No radio button was checked?!");
+    return 0;
+}
+
+void dlg_checkbox_set(union control *ctrl, void *dlg, int checked)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    assert(c && c->ctrl->generic.type == CTRL_CHECKBOX);
+    CheckDlgButton(dp->hwnd, c->base_id, (checked != 0));
+}
+
+int dlg_checkbox_get(union control *ctrl, void *dlg)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    assert(c && c->ctrl->generic.type == CTRL_CHECKBOX);
+    return 0 != IsDlgButtonChecked(dp->hwnd, c->base_id);
+}
+
+void dlg_editbox_set(union control *ctrl, void *dlg, char const *text)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
+    SetDlgItemText(dp->hwnd, c->base_id+1, text);
+}
+
+void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
+    GetDlgItemText(dp->hwnd, c->base_id+1, buffer, length);
+    buffer[length-1] = '\0';
+}
+
+/* The `listbox' functions can also apply to combo boxes. */
+void dlg_listbox_clear(union control *ctrl, void *dlg)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    int msg;
+    assert(c &&
+          (c->ctrl->generic.type == CTRL_LISTBOX ||
+           c->ctrl->generic.type == CTRL_EDITBOX &&
+           c->ctrl->editbox.has_list));
+    msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
+          LB_RESETCONTENT : CB_RESETCONTENT);
+    SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, 0);
+}
+
+void dlg_listbox_del(union control *ctrl, void *dlg, int index)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    int msg;
+    assert(c &&
+          (c->ctrl->generic.type == CTRL_LISTBOX ||
+           c->ctrl->generic.type == CTRL_EDITBOX &&
+           c->ctrl->editbox.has_list));
+    msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
+          LB_DELETESTRING : CB_DELETESTRING);
+    SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0);
+}
+
+void dlg_listbox_add(union control *ctrl, void *dlg, char const *text)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    int msg;
+    assert(c &&
+          (c->ctrl->generic.type == CTRL_LISTBOX ||
+           c->ctrl->generic.type == CTRL_EDITBOX &&
+           c->ctrl->editbox.has_list));
+    msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
+          LB_ADDSTRING : CB_ADDSTRING);
+    SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, (LPARAM)text);
+}
+
+/*
+ * Each listbox entry may have a numeric id associated with it.
+ * Note that some front ends only permit a string to be stored at
+ * each position, which means that _if_ you put two identical
+ * strings in any listbox then you MUST not assign them different
+ * IDs and expect to get meaningful results back.
+ */
+void dlg_listbox_addwithindex(union control *ctrl, void *dlg,
+                             char const *text, int id)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    int msg, msg2, index;
+    assert(c &&
+          (c->ctrl->generic.type == CTRL_LISTBOX ||
+           c->ctrl->generic.type == CTRL_EDITBOX &&
+           c->ctrl->editbox.has_list));
+    msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
+          LB_ADDSTRING : CB_ADDSTRING);
+    msg2 = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
+          LB_SETITEMDATA : CB_SETITEMDATA);
+    index = SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, (LPARAM)text);
+    SendDlgItemMessage(dp->hwnd, c->base_id+1, msg2, index, (LPARAM)id);
+}
+
+int dlg_listbox_getid(union control *ctrl, void *dlg, int index)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    int msg;
+    assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
+    msg = (c->ctrl->listbox.height != 0 ? LB_GETITEMDATA : CB_GETITEMDATA);
+    return
+       SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0);
+}
+
+/* dlg_listbox_index returns <0 if no single element is selected. */
+int dlg_listbox_index(union control *ctrl, void *dlg)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    int msg, ret;
+    assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
+          !c->ctrl->listbox.multisel);
+    msg = (c->ctrl->listbox.height != 0 ? LB_GETCURSEL : CB_GETCURSEL);
+    ret = SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, 0);
+    if (ret == LB_ERR)
+       return -1;
+    else
+       return ret;
+}
+
+int dlg_listbox_issel(union control *ctrl, void *dlg, int index)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
+          c->ctrl->listbox.multisel &&
+          c->ctrl->listbox.height != 0);
+    return
+       SendDlgItemMessage(dp->hwnd, c->base_id+1, LB_GETSEL, index, 0);
+}
+
+void dlg_listbox_select(union control *ctrl, void *dlg, int index)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    int msg;
+    assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
+          !c->ctrl->listbox.multisel);
+    msg = (c->ctrl->listbox.height != 0 ? LB_SETCURSEL : CB_SETCURSEL);
+    SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0);
+}
+
+void dlg_text_set(union control *ctrl, void *dlg, char const *text)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    assert(c && c->ctrl->generic.type == CTRL_TEXT);
+    SetDlgItemText(dp->hwnd, c->base_id, text);
+}
+
+void dlg_filesel_set(union control *ctrl, void *dlg, Filename fn)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    assert(c && c->ctrl->generic.type == CTRL_FILESELECT);
+    SetDlgItemText(dp->hwnd, c->base_id+1, fn.path);
+}
+
+void dlg_filesel_get(union control *ctrl, void *dlg, Filename *fn)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    assert(c && c->ctrl->generic.type == CTRL_FILESELECT);
+    GetDlgItemText(dp->hwnd, c->base_id+1, fn->path, lenof(fn->path));
+    fn->path[lenof(fn->path)-1] = '\0';
+}
+
+void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec fs)
+{
+    char *buf, *boldstr;
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    assert(c && c->ctrl->generic.type == CTRL_FONTSELECT);
+
+    *(FontSpec *)c->data = fs;        /* structure copy */
+
+    boldstr = (fs.isbold ? "bold, " : "");
+    if (fs.height == 0)
+       buf = dupprintf("Font: %s, %sdefault height", fs.name, boldstr);
+    else
+       buf = dupprintf("Font: %s, %s%d-point", fs.name, boldstr,
+                       (fs.height < 0 ? -fs.height : fs.height));
+    SetDlgItemText(dp->hwnd, c->base_id+1, buf);
+    sfree(buf);
+}
+
+void dlg_fontsel_get(union control *ctrl, void *dlg, FontSpec *fs)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    assert(c && c->ctrl->generic.type == CTRL_FONTSELECT);
+    *fs = *(FontSpec *)c->data;               /* structure copy */
+}
+
+/*
+ * Bracketing a large set of updates in these two functions will
+ * cause the front end (if possible) to delay updating the screen
+ * until it's all complete, thus avoiding flicker.
+ */
+void dlg_update_start(union control *ctrl, void *dlg)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    if (c && c->ctrl->generic.type == CTRL_LISTBOX) {
+       SendDlgItemMessage(dp->hwnd, c->base_id+1, WM_SETREDRAW, FALSE, 0);
+    }
+}
+
+void dlg_update_done(union control *ctrl, void *dlg)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    if (c && c->ctrl->generic.type == CTRL_LISTBOX) {
+       HWND hw = GetDlgItem(dp->hwnd, c->base_id+1);
+       SendMessage(hw, WM_SETREDRAW, TRUE, 0);
+       InvalidateRect(hw, NULL, TRUE);
+    }
+}
+
+void dlg_set_focus(union control *ctrl, void *dlg)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    struct winctrl *c = dlg_findbyctrl(dp, ctrl);
+    int id;
+    HWND ctl;
+    switch (ctrl->generic.type) {
+      case CTRL_EDITBOX: id = c->base_id + 1; break;
+      case CTRL_RADIO:
+       for (id = c->base_id + ctrl->radio.nbuttons; id > 1; id--)
+           if (IsDlgButtonChecked(dp->hwnd, id))
+               break;
+       /*
+        * In the theoretically-unlikely case that no button was
+        * selected, id should come out of this as 1, which is a
+        * reasonable enough choice.
+        */
+       break;
+      case CTRL_CHECKBOX: id = c->base_id; break;
+      case CTRL_BUTTON: id = c->base_id; break;
+      case CTRL_LISTBOX: id = c->base_id + 1; break;
+      case CTRL_FILESELECT: id = c->base_id + 1; break;
+      case CTRL_FONTSELECT: id = c->base_id + 2; break;
+      default: id = c->base_id; break;
+    }
+    ctl = GetDlgItem(dp->hwnd, id);
+    SetFocus(ctl);
+}
+
+/*
+ * During event processing, you might well want to give an error
+ * indication to the user. dlg_beep() is a quick and easy generic
+ * error; dlg_error() puts up a message-box or equivalent.
+ */
+void dlg_beep(void *dlg)
+{
+    /* struct dlgparam *dp = (struct dlgparam *)dlg; */
+    MessageBeep(0);
+}
+
+void dlg_error_msg(void *dlg, char *msg)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    MessageBox(dp->hwnd, msg,
+              dp->errtitle ? dp->errtitle : NULL,
+              MB_OK | MB_ICONERROR);
+}
+
+/*
+ * This function signals to the front end that the dialog's
+ * processing is completed, and passes an integer value (typically
+ * a success status).
+ */
+void dlg_end(void *dlg, int value)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    dp->ended = TRUE;
+    dp->endresult = value;
+}
+
+void dlg_refresh(union control *ctrl, void *dlg)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    int i, j;
+    struct winctrl *c;
+
+    if (!ctrl) {
+       /*
+        * Send EVENT_REFRESH to absolutely everything.
+        */
+       for (j = 0; j < dp->nctrltrees; j++) {
+           for (i = 0;
+                (c = winctrl_findbyindex(dp->controltrees[j], i)) != NULL;
+                i++) {
+               if (c->ctrl && c->ctrl->generic.handler != NULL)
+                   c->ctrl->generic.handler(c->ctrl, dp,
+                                            dp->data, EVENT_REFRESH);
+           }
+       }
+    } else {
+       /*
+        * Send EVENT_REFRESH to a specific control.
+        */
+       if (ctrl->generic.handler != NULL)
+           ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH);
+    }
+}
+
+void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    dp->coloursel_wanted = TRUE;
+    dp->coloursel_result.r = r;
+    dp->coloursel_result.g = g;
+    dp->coloursel_result.b = b;
+}
+
+int dlg_coloursel_results(union control *ctrl, void *dlg,
+                         int *r, int *g, int *b)
+{
+    struct dlgparam *dp = (struct dlgparam *)dlg;
+    if (dp->coloursel_result.ok) {
+       *r = dp->coloursel_result.r;
+       *g = dp->coloursel_result.g;
+       *b = dp->coloursel_result.b;
+       return 1;
+    } else
+       return 0;
 }
index 9299a00..ca1890c 100644 (file)
--- a/windlg.c
+++ b/windlg.c
@@ -3,6 +3,8 @@
 #include <commdlg.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <limits.h>
+#include <assert.h>
 #include <ctype.h>
 #include <time.h>
 
@@ -11,6 +13,7 @@
 #include "winstuff.h"
 #include "win_res.h"
 #include "storage.h"
+#include "dialog.h"
 
 #ifdef MSVC4
 #define TVINSERTSTRUCT  TV_INSERTSTRUCT
 #define ICON_BIG        1
 #endif
 
+/*
+ * These are the various bits of data required to handle the
+ * portable-dialog stuff in the config box. Having them at file
+ * scope in here isn't too bad a place to put them; if we were ever
+ * to need more than one config box per process we could always
+ * shift them to a per-config-box structure stored in GWL_USERDATA.
+ */
+static struct controlbox *ctrlbox;
+/*
+ * ctrls_base holds the OK and Cancel buttons: the controls which
+ * are present in all dialog panels. ctrls_panel holds the ones
+ * which change from panel to panel.
+ */
+static struct winctrls ctrls_base, ctrls_panel;
+static struct dlgparam dp;
+
 static char **events = NULL;
 static int nevents = 0, negsize = 0;
 
-static int readytogo;
-static int sesslist_has_focus;
 static int requested_help;
 
-static struct prefslist cipherlist;
-
 extern Config cfg;                    /* defined in window.c */
 
 struct sesslist sesslist;             /* exported to window.c */
@@ -51,31 +66,6 @@ void force_normal(HWND hwnd)
     recurse = 0;
 }
 
-static void MyGetDlgItemInt(HWND hwnd, int id, int *result)
-{
-    BOOL ok;
-    int n;
-    n = GetDlgItemInt(hwnd, id, &ok, FALSE);
-    if (ok)
-       *result = n;
-}
-
-static void MyGetDlgItemFlt(HWND hwnd, int id, int *result, int scale)
-{
-    char text[80];
-    BOOL ok;
-    ok = GetDlgItemText(hwnd, id, text, sizeof(text) - 1);
-    if (ok && text[0])
-       *result = (int) (scale * atof(text));
-}
-
-static void MySetDlgItemFlt(HWND hwnd, int id, double value)
-{
-    char text[80];
-    sprintf(text, "%g", value);
-    SetDlgItemText(hwnd, id, text);
-}
-
 static int CALLBACK LogProc(HWND hwnd, UINT msg,
                            WPARAM wParam, LPARAM lParam)
 {
@@ -231,1222 +221,21 @@ static int CALLBACK NullDlgProc(HWND hwnd, UINT msg,
     return 0;
 }
 
-static char savedsession[2048];
-
-enum { IDCX_ABOUT =
-       IDC_ABOUT, IDCX_TVSTATIC, IDCX_TREEVIEW, controlstartvalue,
-
-    sessionpanelstart,
-    IDC_TITLE_SESSION,
-    IDC_BOX_SESSION1,
-    IDC_BOX_SESSION2,
-    IDC_BOX_SESSION3,
-    IDC_HOSTSTATIC,
-    IDC_HOST,
-    IDC_PORTSTATIC,
-    IDC_PORT,
-    IDC_PROTSTATIC,
-    IDC_PROTRAW,
-    IDC_PROTTELNET,
-    IDC_PROTRLOGIN,
-    IDC_PROTSSH,
-    IDC_SESSSTATIC,
-    IDC_SESSEDIT,
-    IDC_SESSLIST,
-    IDC_SESSLOAD,
-    IDC_SESSSAVE,
-    IDC_SESSDEL,
-    IDC_CLOSEEXIT,
-    IDC_COEALWAYS,
-    IDC_COENEVER,
-    IDC_COENORMAL,
-    sessionpanelend,
-
-    loggingpanelstart,
-    IDC_TITLE_LOGGING,
-    IDC_BOX_LOGGING1,
-    IDC_LSTATSTATIC,
-    IDC_LSTATOFF,
-    IDC_LSTATASCII,
-    IDC_LSTATRAW,
-    IDC_LSTATPACKET,
-    IDC_LGFSTATIC,
-    IDC_LGFEDIT,
-    IDC_LGFBUTTON,
-    IDC_LGFEXPLAIN,
-    IDC_LSTATXIST,
-    IDC_LSTATXOVR,
-    IDC_LSTATXAPN,
-    IDC_LSTATXASK,
-    loggingpanelend,
-
-    keyboardpanelstart,
-    IDC_TITLE_KEYBOARD,
-    IDC_BOX_KEYBOARD1,
-    IDC_BOX_KEYBOARD2,
-    IDC_BOX_KEYBOARD3,
-    IDC_DELSTATIC,
-    IDC_DEL008,
-    IDC_DEL127,
-    IDC_HOMESTATIC,
-    IDC_HOMETILDE,
-    IDC_HOMERXVT,
-    IDC_FUNCSTATIC,
-    IDC_FUNCTILDE,
-    IDC_FUNCLINUX,
-    IDC_FUNCXTERM,
-    IDC_FUNCVT400,
-    IDC_FUNCVT100P,
-    IDC_FUNCSCO,
-    IDC_KPSTATIC,
-    IDC_KPNORMAL,
-    IDC_KPAPPLIC,
-    IDC_KPNH,
-    IDC_CURSTATIC,
-    IDC_CURNORMAL,
-    IDC_CURAPPLIC,
-    IDC_COMPOSEKEY,
-    IDC_CTRLALTKEYS,
-    keyboardpanelend,
-
-    terminalpanelstart,
-    IDC_TITLE_TERMINAL,
-    IDC_BOX_TERMINAL1,
-    IDC_BOX_TERMINAL2,
-    IDC_BOX_TERMINAL3,
-    IDC_WRAPMODE,
-    IDC_DECOM,
-    IDC_LFHASCR,
-    IDC_BCE,
-    IDC_BLINKTEXT,
-    IDC_ANSWERBACK,
-    IDC_ANSWEREDIT,
-    IDC_ECHOSTATIC,
-    IDC_ECHOBACKEND,
-    IDC_ECHOYES,
-    IDC_ECHONO,
-    IDC_EDITSTATIC,
-    IDC_EDITBACKEND,
-    IDC_EDITYES,
-    IDC_EDITNO,
-    IDC_PRINTERSTATIC,
-    IDC_PRINTER,
-    terminalpanelend,
-
-    featurespanelstart,
-    IDC_TITLE_FEATURES,
-    IDC_BOX_FEATURES1,
-    IDC_NOAPPLICK,
-    IDC_NOAPPLICC,
-    IDC_NOMOUSEREP,
-    IDC_NORESIZE,
-    IDC_NOALTSCREEN,
-    IDC_NOWINTITLE,
-    IDC_NODBACKSPACE,
-    IDC_NOCHARSET,
-    featurespanelend,
-
-    bellpanelstart,
-    IDC_TITLE_BELL,
-    IDC_BOX_BELL1,
-    IDC_BOX_BELL2,
-    IDC_BELLSTATIC,
-    IDC_BELL_DISABLED,
-    IDC_BELL_DEFAULT,
-    IDC_BELL_WAVEFILE,
-    IDC_BELL_VISUAL,
-    IDC_BELL_WAVESTATIC,
-    IDC_BELL_WAVEEDIT,
-    IDC_BELL_WAVEBROWSE,
-    IDC_B_IND_STATIC,
-    IDC_B_IND_DISABLED,
-    IDC_B_IND_FLASH,
-    IDC_B_IND_STEADY,
-    IDC_BELLOVL,
-    IDC_BELLOVLNSTATIC,
-    IDC_BELLOVLN,
-    IDC_BELLOVLTSTATIC,
-    IDC_BELLOVLT,
-    IDC_BELLOVLEXPLAIN,
-    IDC_BELLOVLSSTATIC,
-    IDC_BELLOVLS,
-    bellpanelend,
-
-    windowpanelstart,
-    IDC_TITLE_WINDOW,
-    IDC_BOX_WINDOW1,
-    IDC_BOX_WINDOW2,
-    IDC_BOX_WINDOW3,
-    IDC_ROWSSTATIC,
-    IDC_ROWSEDIT,
-    IDC_COLSSTATIC,
-    IDC_COLSEDIT,
-    IDC_RESIZESTATIC,
-    IDC_RESIZETERM,
-    IDC_RESIZEFONT,
-    IDC_RESIZENONE,
-    IDC_RESIZEEITHER,
-    IDC_SCROLLBAR,
-    IDC_SCROLLBARFULLSCREEN,
-    IDC_SAVESTATIC,
-    IDC_SAVEEDIT,
-    IDC_SCROLLKEY,
-    IDC_SCROLLDISP,
-    windowpanelend,
-
-    behaviourpanelstart,
-    IDC_TITLE_BEHAVIOUR,
-    IDC_BOX_BEHAVIOUR1,
-    IDC_CLOSEWARN,
-    IDC_ALTF4,
-    IDC_ALTSPACE,
-    IDC_ALTONLY,
-    IDC_ALWAYSONTOP,
-    IDC_FULLSCREENONALTENTER,
-    behaviourpanelend,
-
-    appearancepanelstart,
-    IDC_TITLE_APPEARANCE,
-    IDC_BOX_APPEARANCE1,
-    IDC_BOX_APPEARANCE2,
-    IDC_BOX_APPEARANCE3,
-    IDC_BOX_APPEARANCE4,
-    IDC_BOX_APPEARANCE5,
-    IDC_CURSORSTATIC,
-    IDC_CURBLOCK,
-    IDC_CURUNDER,
-    IDC_CURVERT,
-    IDC_BLINKCUR,
-    IDC_FONTSTATIC,
-    IDC_CHOOSEFONT,
-    IDC_WINTITLE,
-    IDC_WINEDIT,
-    IDC_WINNAME,
-    IDC_HIDEMOUSE,
-    IDC_SUNKENEDGE,
-    IDC_WINBSTATIC,
-    IDC_WINBEDIT,
-    appearancepanelend,
-
-    connectionpanelstart,
-    IDC_TITLE_CONNECTION,
-    IDC_BOX_CONNECTION1,
-    IDC_BOX_CONNECTION2,
-    IDC_BOX_CONNECTION3,
-    IDC_TTSTATIC,
-    IDC_TTEDIT,
-    IDC_LOGSTATIC,
-    IDC_LOGEDIT,
-    IDC_PINGSTATIC,
-    IDC_PINGEDIT,
-    IDC_NODELAY,
-    connectionpanelend,
-
-    proxypanelstart,
-    IDC_TITLE_PROXY,
-    IDC_BOX_PROXY1,
-    IDC_PROXYTYPESTATIC,
-    IDC_PROXYTYPENONE,
-    IDC_PROXYTYPEHTTP,
-    IDC_PROXYTYPESOCKS,
-    IDC_PROXYTYPETELNET,
-    IDC_PROXYHOSTSTATIC,
-    IDC_PROXYHOSTEDIT,
-    IDC_PROXYPORTSTATIC,
-    IDC_PROXYPORTEDIT,
-    IDC_PROXYEXCLUDESTATIC,
-    IDC_PROXYEXCLUDEEDIT,
-    IDC_PROXYLOCALHOST,
-    IDC_PROXYDNSSTATIC,
-    IDC_PROXYDNSNO,
-    IDC_PROXYDNSAUTO,
-    IDC_PROXYDNSYES,
-    IDC_PROXYUSERSTATIC,
-    IDC_PROXYUSEREDIT,
-    IDC_PROXYPASSSTATIC,
-    IDC_PROXYPASSEDIT,
-    IDC_BOX_PROXY2,
-    IDC_PROXYTELNETCMDSTATIC,
-    IDC_PROXYTELNETCMDEDIT,
-    IDC_PROXYSOCKSVERSTATIC,
-    IDC_PROXYSOCKSVER5,
-    IDC_PROXYSOCKSVER4,
-    proxypanelend,
-
-    telnetpanelstart,
-    IDC_TITLE_TELNET,
-    IDC_BOX_TELNET1,
-    IDC_BOX_TELNET2,
-    IDC_TSSTATIC,
-    IDC_TSEDIT,
-    IDC_ENVSTATIC,
-    IDC_VARSTATIC,
-    IDC_VAREDIT,
-    IDC_VALSTATIC,
-    IDC_VALEDIT,
-    IDC_ENVLIST,
-    IDC_ENVADD,
-    IDC_ENVREMOVE,
-    IDC_EMSTATIC,
-    IDC_EMBSD,
-    IDC_EMRFC,
-    IDC_ACTSTATIC,
-    IDC_TPASSIVE,
-    IDC_TACTIVE,
-    IDC_TELNETKEY,
-    IDC_TELNETRET,
-    telnetpanelend,
-
-    rloginpanelstart,
-    IDC_TITLE_RLOGIN,
-    IDC_BOX_RLOGIN1,
-    IDC_BOX_RLOGIN2,
-    IDC_R_TSSTATIC,
-    IDC_R_TSEDIT,
-    IDC_RLLUSERSTATIC,
-    IDC_RLLUSEREDIT,
-    rloginpanelend,
-
-    sshpanelstart,
-    IDC_TITLE_SSH,
-    IDC_BOX_SSH1,
-    IDC_BOX_SSH2,
-    IDC_BOX_SSH3,
-    IDC_NOPTY,
-    IDC_BOX_SSHCIPHER,
-    IDC_CIPHERSTATIC2,
-    IDC_CIPHERLIST,
-    IDC_CIPHERUP,
-    IDC_CIPHERDN,
-    IDC_SSH2DES,
-    IDC_SSHPROTSTATIC,
-    IDC_SSHPROT1ONLY,
-    IDC_SSHPROT1,
-    IDC_SSHPROT2,
-    IDC_SSHPROT2ONLY,
-    IDC_CMDSTATIC,
-    IDC_CMDEDIT,
-    IDC_COMPRESS,
-    sshpanelend,
-
-    sshauthpanelstart,
-    IDC_TITLE_SSHAUTH,
-    IDC_BOX_SSHAUTH1,
-    IDC_BOX_SSHAUTH2,
-    IDC_PKSTATIC,
-    IDC_PKEDIT,
-    IDC_PKBUTTON,
-    IDC_AGENTFWD,
-    IDC_CHANGEUSER,
-    IDC_AUTHTIS,
-    IDC_AUTHKI,
-    sshauthpanelend,
-
-    sshbugspanelstart,
-    IDC_TITLE_SSHBUGS,
-    IDC_BOX_SSHBUGS1,
-    IDC_BUGS_IGNORE1,
-    IDC_BUGD_IGNORE1,
-    IDC_BUGS_PLAINPW1,
-    IDC_BUGD_PLAINPW1,
-    IDC_BUGS_RSA1,
-    IDC_BUGD_RSA1,
-    IDC_BUGS_HMAC2,
-    IDC_BUGD_HMAC2,
-    IDC_BUGS_DERIVEKEY2,
-    IDC_BUGD_DERIVEKEY2,
-    IDC_BUGS_RSAPAD2,
-    IDC_BUGD_RSAPAD2,
-    IDC_BUGS_DHGEX2,
-    IDC_BUGD_DHGEX2,
-    IDC_BUGS_PKSESSID2,
-    IDC_BUGD_PKSESSID2,
-    sshbugspanelend,
-
-    selectionpanelstart,
-    IDC_TITLE_SELECTION,
-    IDC_BOX_SELECTION1,
-    IDC_BOX_SELECTION2,
-    IDC_BOX_SELECTION3,
-    IDC_MBSTATIC,
-    IDC_MBWINDOWS,
-    IDC_MBXTERM,
-    IDC_MOUSEOVERRIDE,
-    IDC_SELTYPESTATIC,
-    IDC_SELTYPELEX,
-    IDC_SELTYPERECT,
-    IDC_CCSTATIC,
-    IDC_CCLIST,
-    IDC_CCSET,
-    IDC_CCSTATIC2,
-    IDC_CCEDIT,
-    IDC_RAWCNP,
-    IDC_RTFPASTE,
-    selectionpanelend,
-
-    colourspanelstart,
-    IDC_TITLE_COLOURS,
-    IDC_BOX_COLOURS1,
-    IDC_BOX_COLOURS2,
-    IDC_BOLDCOLOUR,
-    IDC_PALETTE,
-    IDC_COLOURSTATIC,
-    IDC_COLOURLIST,
-    IDC_RSTATIC,
-    IDC_GSTATIC,
-    IDC_BSTATIC,
-    IDC_RVALUE,
-    IDC_GVALUE,
-    IDC_BVALUE,
-    IDC_CHANGE,
-    colourspanelend,
-
-    translationpanelstart,
-    IDC_TITLE_TRANSLATION,
-    IDC_BOX_TRANSLATION1,
-    IDC_BOX_TRANSLATION2,
-    IDC_BOX_TRANSLATION3,
-    IDC_CODEPAGESTATIC,
-    IDC_CODEPAGE,
-    IDC_CAPSLOCKCYR,
-    IDC_VTSTATIC,
-    IDC_VTXWINDOWS,
-    IDC_VTOEMANSI,
-    IDC_VTOEMONLY,
-    IDC_VTPOORMAN,
-    IDC_VTUNICODE,
-    translationpanelend,
-
-    tunnelspanelstart,
-    IDC_TITLE_TUNNELS,
-    IDC_BOX_TUNNELS1,
-    IDC_BOX_TUNNELS2,
-    IDC_X11_FORWARD,
-    IDC_X11_DISPSTATIC,
-    IDC_X11_DISPLAY,
-    IDC_X11AUTHSTATIC,
-    IDC_X11MIT,
-    IDC_X11XDM,
-    IDC_LPORT_ALL,
-    IDC_RPORT_ALL,
-    IDC_PFWDSTATIC,
-    IDC_PFWDSTATIC2,
-    IDC_PFWDREMOVE,
-    IDC_PFWDLIST,
-    IDC_PFWDADD,
-    IDC_SPORTSTATIC,
-    IDC_SPORTEDIT,
-    IDC_DPORTSTATIC,
-    IDC_DPORTEDIT,
-    IDC_PFWDLOCAL,
-    IDC_PFWDREMOTE,
-
-    tunnelspanelend,
-
-    controlendvalue
-};
-
-static const char *const colours[] = {
-    "Default Foreground", "Default Bold Foreground",
-    "Default Background", "Default Bold Background",
-    "Cursor Text", "Cursor Colour",
-    "ANSI Black", "ANSI Black Bold",
-    "ANSI Red", "ANSI Red Bold",
-    "ANSI Green", "ANSI Green Bold",
-    "ANSI Yellow", "ANSI Yellow Bold",
-    "ANSI Blue", "ANSI Blue Bold",
-    "ANSI Magenta", "ANSI Magenta Bold",
-    "ANSI Cyan", "ANSI Cyan Bold",
-    "ANSI White", "ANSI White Bold"
+enum {
+    IDCX_ABOUT = IDC_ABOUT,
+    IDCX_TVSTATIC,
+    IDCX_TREEVIEW,
+    IDCX_STDBASE,
+    IDCX_PANELBASE = IDCX_STDBASE + 32
 };
 
-static void fmtfont(char *buf)
-{
-    sprintf(buf, "Font: %s, ", cfg.font.name);
-    if (cfg.font.isbold)
-       strcat(buf, "bold, ");
-    if (cfg.font.height == 0)
-       strcat(buf, "default height");
-    else
-       sprintf(buf + strlen(buf), "%d-point",
-               (cfg.font.height < 0 ? -cfg.font.height : cfg.font.height));
-}
-
-char *help_context_cmd(int id)
-{
-    switch (id) {
-      case IDC_HOSTSTATIC:
-      case IDC_HOST:
-      case IDC_PORTSTATIC:
-      case IDC_PORT:
-      case IDC_PROTSTATIC:
-      case IDC_PROTRAW:
-      case IDC_PROTTELNET:
-      case IDC_PROTRLOGIN:
-      case IDC_PROTSSH:
-        return "JI(`',`session.hostname')";
-      case IDC_SESSSTATIC:
-      case IDC_SESSEDIT:
-      case IDC_SESSLIST:
-      case IDC_SESSLOAD:
-      case IDC_SESSSAVE:
-      case IDC_SESSDEL:
-        return "JI(`',`session.saved')";
-      case IDC_CLOSEEXIT:
-      case IDC_COEALWAYS:
-      case IDC_COENEVER:
-      case IDC_COENORMAL:
-        return "JI(`',`session.coe')";
-      case IDC_LSTATSTATIC:
-      case IDC_LSTATOFF:
-      case IDC_LSTATASCII:
-      case IDC_LSTATRAW:
-      case IDC_LSTATPACKET:
-        return "JI(`',`logging.main')";
-      case IDC_LGFSTATIC:
-      case IDC_LGFEDIT:
-      case IDC_LGFBUTTON:
-      case IDC_LGFEXPLAIN:
-        return "JI(`',`logging.filename')";
-      case IDC_LSTATXIST:
-      case IDC_LSTATXOVR:
-      case IDC_LSTATXAPN:
-      case IDC_LSTATXASK:
-        return "JI(`',`logging.exists')";
-
-      case IDC_DELSTATIC:
-      case IDC_DEL008:
-      case IDC_DEL127:
-        return "JI(`',`keyboard.backspace')";
-      case IDC_HOMESTATIC:
-      case IDC_HOMETILDE:
-      case IDC_HOMERXVT:
-        return "JI(`',`keyboard.homeend')";
-      case IDC_FUNCSTATIC:
-      case IDC_FUNCTILDE:
-      case IDC_FUNCLINUX:
-      case IDC_FUNCXTERM:
-      case IDC_FUNCVT400:
-      case IDC_FUNCVT100P:
-      case IDC_FUNCSCO:
-        return "JI(`',`keyboard.funkeys')";
-      case IDC_KPSTATIC:
-      case IDC_KPNORMAL:
-      case IDC_KPAPPLIC:
-        return "JI(`',`keyboard.appkeypad')";
-      case IDC_CURSTATIC:
-      case IDC_CURNORMAL:
-      case IDC_CURAPPLIC:
-        return "JI(`',`keyboard.appcursor')";
-      case IDC_KPNH:
-        return "JI(`',`keyboard.nethack')";
-      case IDC_COMPOSEKEY:
-        return "JI(`',`keyboard.compose')";
-      case IDC_CTRLALTKEYS:
-        return "JI(`',`keyboard.ctrlalt')";
-
-      case IDC_NOAPPLICK:
-      case IDC_NOAPPLICC:
-        return "JI(`',`features.application')";
-      case IDC_NOMOUSEREP:
-        return "JI(`',`features.mouse')";
-      case IDC_NORESIZE:
-        return "JI(`',`features.resize')";
-      case IDC_NOALTSCREEN:
-        return "JI(`',`features.altscreen')";
-      case IDC_NOWINTITLE:
-        return "JI(`',`features.retitle')";
-      case IDC_NODBACKSPACE:
-        return "JI(`',`features.dbackspace')";
-      case IDC_NOCHARSET:
-        return "JI(`',`features.charset')";
-
-      case IDC_WRAPMODE:
-        return "JI(`',`terminal.autowrap')";
-      case IDC_DECOM:
-        return "JI(`',`terminal.decom')";
-      case IDC_LFHASCR:
-        return "JI(`',`terminal.lfhascr')";
-      case IDC_BCE:
-        return "JI(`',`terminal.bce')";
-      case IDC_BLINKTEXT:
-        return "JI(`',`terminal.blink')";
-      case IDC_ANSWERBACK:
-      case IDC_ANSWEREDIT:
-        return "JI(`',`terminal.answerback')";
-      case IDC_ECHOSTATIC:
-      case IDC_ECHOBACKEND:
-      case IDC_ECHOYES:
-      case IDC_ECHONO:
-        return "JI(`',`terminal.localecho')";
-      case IDC_EDITSTATIC:
-      case IDC_EDITBACKEND:
-      case IDC_EDITYES:
-      case IDC_EDITNO:
-        return "JI(`',`terminal.localedit')";
-      case IDC_PRINTERSTATIC:
-      case IDC_PRINTER:
-       return "JI(`',`terminal.printing')";
-
-      case IDC_BELLSTATIC:
-      case IDC_BELL_DISABLED:
-      case IDC_BELL_DEFAULT:
-      case IDC_BELL_WAVEFILE:
-      case IDC_BELL_VISUAL:
-      case IDC_BELL_WAVESTATIC:
-      case IDC_BELL_WAVEEDIT:
-      case IDC_BELL_WAVEBROWSE:
-        return "JI(`',`bell.style')";
-      case IDC_B_IND_STATIC:
-      case IDC_B_IND_DISABLED:
-      case IDC_B_IND_FLASH:
-      case IDC_B_IND_STEADY:
-        return "JI(`',`bell.taskbar')";
-      case IDC_BELLOVL:
-      case IDC_BELLOVLNSTATIC:
-      case IDC_BELLOVLN:
-      case IDC_BELLOVLTSTATIC:
-      case IDC_BELLOVLT:
-      case IDC_BELLOVLEXPLAIN:
-      case IDC_BELLOVLSSTATIC:
-      case IDC_BELLOVLS:
-        return "JI(`',`bell.overload')";
-
-      case IDC_ROWSSTATIC:
-      case IDC_ROWSEDIT:
-      case IDC_COLSSTATIC:
-      case IDC_COLSEDIT:
-        return "JI(`',`window.size')";
-      case IDC_RESIZESTATIC:
-      case IDC_RESIZETERM:
-      case IDC_RESIZEFONT:
-      case IDC_RESIZENONE:
-      case IDC_RESIZEEITHER:
-        return "JI(`',`window.resize')";
-      case IDC_SCROLLBAR:
-      case IDC_SCROLLBARFULLSCREEN:
-      case IDC_SAVESTATIC:
-      case IDC_SAVEEDIT:
-      case IDC_SCROLLKEY:
-      case IDC_SCROLLDISP:
-        return "JI(`',`window.scrollback')";
-
-      case IDC_CLOSEWARN:
-        return "JI(`',`behaviour.closewarn')";
-      case IDC_ALTF4:
-        return "JI(`',`behaviour.altf4')";
-      case IDC_ALTSPACE:
-        return "JI(`',`behaviour.altspace')";
-      case IDC_ALTONLY:
-        return "JI(`',`behaviour.altonly')";
-      case IDC_ALWAYSONTOP:
-        return "JI(`',`behaviour.alwaysontop')";
-      case IDC_FULLSCREENONALTENTER:
-        return "JI(`',`behaviour.altenter')";
-
-      case IDC_CURSORSTATIC:
-      case IDC_CURBLOCK:
-      case IDC_CURUNDER:
-      case IDC_CURVERT:
-      case IDC_BLINKCUR:
-        return "JI(`',`appearance.cursor')";
-      case IDC_FONTSTATIC:
-      case IDC_CHOOSEFONT:
-        return "JI(`',`appearance.font')";
-      case IDC_WINTITLE:
-      case IDC_WINEDIT:
-      case IDC_WINNAME:
-        return "JI(`',`appearance.title')";
-      case IDC_HIDEMOUSE:
-        return "JI(`',`appearance.hidemouse')";
-      case IDC_SUNKENEDGE:
-      case IDC_WINBSTATIC:
-      case IDC_WINBEDIT:
-        return "JI(`',`appearance.border')";
-
-      case IDC_TTSTATIC:
-      case IDC_TTEDIT:
-        return "JI(`',`connection.termtype')";
-      case IDC_LOGSTATIC:
-      case IDC_LOGEDIT:
-        return "JI(`',`connection.username')";
-      case IDC_PINGSTATIC:
-      case IDC_PINGEDIT:
-        return "JI(`',`connection.keepalive')";
-      case IDC_NODELAY:
-        return "JI(`',`connection.nodelay')";
-
-      case IDC_PROXYTYPESTATIC:
-      case IDC_PROXYTYPENONE:
-      case IDC_PROXYTYPEHTTP:
-      case IDC_PROXYTYPESOCKS:
-      case IDC_PROXYTYPETELNET:
-        return "JI(`',`proxy.type')";
-      case IDC_PROXYHOSTSTATIC:
-      case IDC_PROXYHOSTEDIT:
-      case IDC_PROXYPORTSTATIC:
-      case IDC_PROXYPORTEDIT:
-        return "JI(`',`proxy.main')";
-      case IDC_PROXYEXCLUDESTATIC:
-      case IDC_PROXYEXCLUDEEDIT:
-      case IDC_PROXYLOCALHOST:
-        return "JI(`',`proxy.exclude')";
-      case IDC_PROXYDNSSTATIC:
-      case IDC_PROXYDNSNO:
-      case IDC_PROXYDNSAUTO:
-      case IDC_PROXYDNSYES:
-       return "JI(`',`proxy.dns')";
-      case IDC_PROXYUSERSTATIC:
-      case IDC_PROXYUSEREDIT:
-      case IDC_PROXYPASSSTATIC:
-      case IDC_PROXYPASSEDIT:
-        return "JI(`',`proxy.auth')";
-      case IDC_PROXYTELNETCMDSTATIC:
-      case IDC_PROXYTELNETCMDEDIT:
-        return "JI(`',`proxy.command')";
-      case IDC_PROXYSOCKSVERSTATIC:
-      case IDC_PROXYSOCKSVER5:
-      case IDC_PROXYSOCKSVER4:
-        return "JI(`',`proxy.socksver')";
-
-      case IDC_TSSTATIC:
-      case IDC_TSEDIT:
-        return "JI(`',`telnet.termspeed')";
-      case IDC_ENVSTATIC:
-      case IDC_VARSTATIC:
-      case IDC_VAREDIT:
-      case IDC_VALSTATIC:
-      case IDC_VALEDIT:
-      case IDC_ENVLIST:
-      case IDC_ENVADD:
-      case IDC_ENVREMOVE:
-        return "JI(`',`telnet.environ')";
-      case IDC_EMSTATIC:
-      case IDC_EMBSD:
-      case IDC_EMRFC:
-        return "JI(`',`telnet.oldenviron')";
-      case IDC_ACTSTATIC:
-      case IDC_TPASSIVE:
-      case IDC_TACTIVE:
-        return "JI(`',`telnet.passive')";
-      case IDC_TELNETKEY:
-        return "JI(`',`telnet.specialkeys')";
-      case IDC_TELNETRET:
-        return "JI(`',`telnet.newline')";
-
-      case IDC_R_TSSTATIC:
-      case IDC_R_TSEDIT:
-        return "JI(`',`rlogin.termspeed')";
-      case IDC_RLLUSERSTATIC:
-      case IDC_RLLUSEREDIT:
-        return "JI(`',`rlogin.localuser')";
-
-      case IDC_NOPTY:
-        return "JI(`',`ssh.nopty')";
-      case IDC_CIPHERSTATIC2:
-      case IDC_CIPHERLIST:
-      case IDC_CIPHERUP:
-      case IDC_CIPHERDN:
-      case IDC_SSH2DES:
-        return "JI(`',`ssh.ciphers')";
-      case IDC_SSHPROTSTATIC:
-      case IDC_SSHPROT1ONLY:
-      case IDC_SSHPROT1:
-      case IDC_SSHPROT2:
-      case IDC_SSHPROT2ONLY:
-        return "JI(`',`ssh.protocol')";
-      case IDC_CMDSTATIC:
-      case IDC_CMDEDIT:
-        return "JI(`',`ssh.command')";
-      case IDC_COMPRESS:
-        return "JI(`',`ssh.compress')";
-
-      case IDC_PKSTATIC:
-      case IDC_PKEDIT:
-      case IDC_PKBUTTON:
-        return "JI(`',`ssh.auth.privkey')";
-      case IDC_AGENTFWD:
-        return "JI(`',`ssh.auth.agentfwd')";
-      case IDC_CHANGEUSER:
-        return "JI(`',`ssh.auth.changeuser')";
-      case IDC_AUTHTIS:
-        return "JI(`',`ssh.auth.tis')";
-      case IDC_AUTHKI:
-        return "JI(`',`ssh.auth.ki')";
-
-      case IDC_MBSTATIC:
-      case IDC_MBWINDOWS:
-      case IDC_MBXTERM:
-        return "JI(`',`selection.buttons')";
-      case IDC_MOUSEOVERRIDE:
-        return "JI(`',`selection.shiftdrag')";
-      case IDC_SELTYPESTATIC:
-      case IDC_SELTYPELEX:
-      case IDC_SELTYPERECT:
-        return "JI(`',`selection.rect')";
-      case IDC_CCSTATIC:
-      case IDC_CCLIST:
-      case IDC_CCSET:
-      case IDC_CCSTATIC2:
-      case IDC_CCEDIT:
-        return "JI(`',`selection.charclasses')";
-      case IDC_RAWCNP:
-        return "JI(`',`selection.linedraw')";
-      case IDC_RTFPASTE:
-        return "JI(`',`selection.rtf')";
-
-      case IDC_BOLDCOLOUR:
-        return "JI(`',`colours.bold')";
-      case IDC_PALETTE:
-        return "JI(`',`colours.logpal')";
-      case IDC_COLOURSTATIC:
-      case IDC_COLOURLIST:
-      case IDC_RSTATIC:
-      case IDC_GSTATIC:
-      case IDC_BSTATIC:
-      case IDC_RVALUE:
-      case IDC_GVALUE:
-      case IDC_BVALUE:
-      case IDC_CHANGE:
-        return "JI(`',`colours.config')";
-
-      case IDC_CODEPAGESTATIC:
-      case IDC_CODEPAGE:
-        return "JI(`',`translation.codepage')";
-      case IDC_CAPSLOCKCYR:
-        return "JI(`',`translation.cyrillic')";
-      case IDC_VTSTATIC:
-      case IDC_VTXWINDOWS:
-      case IDC_VTOEMANSI:
-      case IDC_VTOEMONLY:
-      case IDC_VTPOORMAN:
-      case IDC_VTUNICODE:
-        return "JI(`',`translation.linedraw')";
-
-      case IDC_X11_FORWARD:
-      case IDC_X11_DISPSTATIC:
-      case IDC_X11_DISPLAY:
-        return "JI(`',`ssh.tunnels.x11')";
-      case IDC_X11AUTHSTATIC:
-      case IDC_X11MIT:
-      case IDC_X11XDM:
-       return "JI(`',`ssh.tunnels.x11auth')";
-      case IDC_PFWDSTATIC:
-      case IDC_PFWDSTATIC2:
-      case IDC_PFWDREMOVE:
-      case IDC_PFWDLIST:
-      case IDC_PFWDADD:
-      case IDC_SPORTSTATIC:
-      case IDC_SPORTEDIT:
-      case IDC_DPORTSTATIC:
-      case IDC_DPORTEDIT:
-      case IDC_PFWDLOCAL:
-      case IDC_PFWDREMOTE:
-        return "JI(`',`ssh.tunnels.portfwd')";
-      case IDC_LPORT_ALL:
-      case IDC_RPORT_ALL:
-        return "JI(`',`ssh.tunnels.portfwd.localhost')";
-
-      case IDC_BUGS_IGNORE1:
-      case IDC_BUGD_IGNORE1:
-       return "JI(`',`ssh.bugs.ignore1')";
-      case IDC_BUGS_PLAINPW1:
-      case IDC_BUGD_PLAINPW1:
-       return "JI(`',`ssh.bugs.plainpw1')";
-      case IDC_BUGS_RSA1:
-      case IDC_BUGD_RSA1:
-       return "JI(`',`ssh.bugs.rsa1')";
-      case IDC_BUGS_HMAC2:
-      case IDC_BUGD_HMAC2:
-       return "JI(`',`ssh.bugs.hmac2')";
-      case IDC_BUGS_DERIVEKEY2:
-      case IDC_BUGD_DERIVEKEY2:
-       return "JI(`',`ssh.bugs.derivekey2')";
-      case IDC_BUGS_RSAPAD2:
-      case IDC_BUGD_RSAPAD2:
-       return "JI(`',`ssh.bugs.rsapad2')";
-      case IDC_BUGS_DHGEX2:
-      case IDC_BUGD_DHGEX2:
-       return "JI(`',`ssh.bugs.dhgex2')";
-      case IDC_BUGS_PKSESSID2:
-      case IDC_BUGD_PKSESSID2:
-       return "JI(`',`ssh.bugs.pksessid2')";
-
-      default:
-        return NULL;
-    }
-}
-
-/* 2nd arg: NZ => don't redraw session list (use when loading
- * a new session) */
-static void init_dlg_ctrls(HWND hwnd, int keepsess)
-{
-    int i;
-    char fontstatic[256];
-
-    SetDlgItemText(hwnd, IDC_HOST, cfg.host);
-    SetDlgItemText(hwnd, IDC_SESSEDIT, savedsession);
-    if (!keepsess) {
-       int i, n;
-       n = SendDlgItemMessage(hwnd, IDC_SESSLIST, LB_GETCOUNT, 0, 0);
-       for (i = n; i-- > 0;)
-           SendDlgItemMessage(hwnd, IDC_SESSLIST, LB_DELETESTRING, i, 0);
-       for (i = 0; i < sesslist.nsessions; i++)
-           SendDlgItemMessage(hwnd, IDC_SESSLIST, LB_ADDSTRING,
-                              0, (LPARAM) (sesslist.sessions[i]));
-    }
-    SetDlgItemInt(hwnd, IDC_PORT, cfg.port, FALSE);
-    CheckRadioButton(hwnd, IDC_PROTRAW, IDC_PROTSSH,
-                    cfg.protocol == PROT_SSH ? IDC_PROTSSH :
-                    cfg.protocol == PROT_TELNET ? IDC_PROTTELNET :
-                    cfg.protocol ==
-                    PROT_RLOGIN ? IDC_PROTRLOGIN : IDC_PROTRAW);
-    SetDlgItemInt(hwnd, IDC_PINGEDIT, cfg.ping_interval, FALSE);
-    CheckDlgButton(hwnd, IDC_NODELAY, cfg.tcp_nodelay);
-
-    CheckRadioButton(hwnd, IDC_DEL008, IDC_DEL127,
-                    cfg.bksp_is_delete ? IDC_DEL127 : IDC_DEL008);
-    CheckRadioButton(hwnd, IDC_HOMETILDE, IDC_HOMERXVT,
-                    cfg.rxvt_homeend ? IDC_HOMERXVT : IDC_HOMETILDE);
-    CheckRadioButton(hwnd, IDC_FUNCTILDE, IDC_FUNCSCO,
-                    cfg.funky_type == 0 ? IDC_FUNCTILDE :
-                    cfg.funky_type == 1 ? IDC_FUNCLINUX :
-                    cfg.funky_type == 2 ? IDC_FUNCXTERM :
-                    cfg.funky_type == 3 ? IDC_FUNCVT400 :
-                    cfg.funky_type == 4 ? IDC_FUNCVT100P :
-                    cfg.funky_type == 5 ? IDC_FUNCSCO : IDC_FUNCTILDE);
-    CheckDlgButton(hwnd, IDC_NOAPPLICC, cfg.no_applic_c);
-    CheckDlgButton(hwnd, IDC_NOAPPLICK, cfg.no_applic_k);
-    CheckDlgButton(hwnd, IDC_NOMOUSEREP, cfg.no_mouse_rep);
-    CheckDlgButton(hwnd, IDC_NORESIZE, cfg.no_remote_resize);
-    CheckDlgButton(hwnd, IDC_NOALTSCREEN, cfg.no_alt_screen);
-    CheckDlgButton(hwnd, IDC_NOWINTITLE, cfg.no_remote_wintitle);
-    CheckDlgButton(hwnd, IDC_NODBACKSPACE, cfg.no_dbackspace);
-    CheckDlgButton(hwnd, IDC_NOCHARSET, cfg.no_remote_charset);
-    CheckRadioButton(hwnd, IDC_CURNORMAL, IDC_CURAPPLIC,
-                    cfg.app_cursor ? IDC_CURAPPLIC : IDC_CURNORMAL);
-    CheckRadioButton(hwnd, IDC_KPNORMAL, IDC_KPNH,
-                    cfg.nethack_keypad ? IDC_KPNH :
-                    cfg.app_keypad ? IDC_KPAPPLIC : IDC_KPNORMAL);
-    CheckDlgButton(hwnd, IDC_ALTF4, cfg.alt_f4);
-    CheckDlgButton(hwnd, IDC_ALTSPACE, cfg.alt_space);
-    CheckDlgButton(hwnd, IDC_ALTONLY, cfg.alt_only);
-    CheckDlgButton(hwnd, IDC_COMPOSEKEY, cfg.compose_key);
-    CheckDlgButton(hwnd, IDC_CTRLALTKEYS, cfg.ctrlaltkeys);
-    CheckDlgButton(hwnd, IDC_TELNETKEY, cfg.telnet_keyboard);
-    CheckDlgButton(hwnd, IDC_TELNETRET, cfg.telnet_newline);
-    CheckRadioButton(hwnd, IDC_ECHOBACKEND, IDC_ECHONO,
-                    cfg.localecho == AUTO ? IDC_ECHOBACKEND :
-                    cfg.localecho == FORCE_ON ? IDC_ECHOYES : IDC_ECHONO);
-    CheckRadioButton(hwnd, IDC_EDITBACKEND, IDC_EDITNO,
-                    cfg.localedit == AUTO ? IDC_EDITBACKEND :
-                    cfg.localedit == FORCE_ON ? IDC_EDITYES : IDC_EDITNO);
-    SetDlgItemText(hwnd, IDC_ANSWEREDIT, cfg.answerback);
-    CheckDlgButton(hwnd, IDC_ALWAYSONTOP, cfg.alwaysontop);
-    CheckDlgButton(hwnd, IDC_FULLSCREENONALTENTER, cfg.fullscreenonaltenter);
-    CheckDlgButton(hwnd, IDC_SCROLLKEY, cfg.scroll_on_key);
-    CheckDlgButton(hwnd, IDC_SCROLLDISP, cfg.scroll_on_disp);
-
-    CheckDlgButton(hwnd, IDC_WRAPMODE, cfg.wrap_mode);
-    CheckDlgButton(hwnd, IDC_DECOM, cfg.dec_om);
-    CheckDlgButton(hwnd, IDC_LFHASCR, cfg.lfhascr);
-    SetDlgItemInt(hwnd, IDC_ROWSEDIT, cfg.height, FALSE);
-    SetDlgItemInt(hwnd, IDC_COLSEDIT, cfg.width, FALSE);
-    SetDlgItemInt(hwnd, IDC_SAVEEDIT, cfg.savelines, FALSE);
-    fmtfont(fontstatic);
-    SetDlgItemText(hwnd, IDC_FONTSTATIC, fontstatic);
-    CheckRadioButton(hwnd, IDC_BELL_DISABLED, IDC_BELL_VISUAL,
-                    cfg.beep == BELL_DISABLED ? IDC_BELL_DISABLED :
-                    cfg.beep == BELL_DEFAULT ? IDC_BELL_DEFAULT :
-                    cfg.beep == BELL_WAVEFILE ? IDC_BELL_WAVEFILE :
-                    cfg.beep ==
-                    BELL_VISUAL ? IDC_BELL_VISUAL : IDC_BELL_DEFAULT);
-    CheckRadioButton(hwnd, IDC_B_IND_DISABLED, IDC_B_IND_STEADY,
-                    cfg.beep_ind ==
-                    B_IND_DISABLED ? IDC_B_IND_DISABLED : cfg.beep_ind ==
-                    B_IND_FLASH ? IDC_B_IND_FLASH : cfg.beep_ind ==
-                    B_IND_STEADY ? IDC_B_IND_STEADY : IDC_B_IND_DISABLED);
-    SetDlgItemText(hwnd, IDC_BELL_WAVEEDIT, cfg.bell_wavefile.path);
-    CheckDlgButton(hwnd, IDC_BELLOVL, cfg.bellovl);
-    SetDlgItemInt(hwnd, IDC_BELLOVLN, cfg.bellovl_n, FALSE);
-    MySetDlgItemFlt(hwnd, IDC_BELLOVLT, cfg.bellovl_t / 1000.0);
-    MySetDlgItemFlt(hwnd, IDC_BELLOVLS, cfg.bellovl_s / 1000.0);
-
-    CheckDlgButton(hwnd, IDC_BCE, cfg.bce);
-    CheckDlgButton(hwnd, IDC_BLINKTEXT, cfg.blinktext);
-
-    SetDlgItemText(hwnd, IDC_WINEDIT, cfg.wintitle);
-    CheckDlgButton(hwnd, IDC_WINNAME, !cfg.win_name_always);
-    CheckDlgButton(hwnd, IDC_HIDEMOUSE, cfg.hide_mouseptr);
-    CheckDlgButton(hwnd, IDC_SUNKENEDGE, cfg.sunken_edge);
-    SetDlgItemInt(hwnd, IDC_WINBEDIT, cfg.window_border, FALSE);
-    CheckRadioButton(hwnd, IDC_CURBLOCK, IDC_CURVERT,
-                    cfg.cursor_type == 0 ? IDC_CURBLOCK :
-                    cfg.cursor_type == 1 ? IDC_CURUNDER : IDC_CURVERT);
-    CheckDlgButton(hwnd, IDC_BLINKCUR, cfg.blink_cur);
-    CheckDlgButton(hwnd, IDC_SCROLLBAR, cfg.scrollbar);
-    CheckDlgButton(hwnd, IDC_SCROLLBARFULLSCREEN, cfg.scrollbar_in_fullscreen);
-    CheckRadioButton(hwnd, IDC_RESIZETERM, IDC_RESIZEEITHER,
-                    cfg.resize_action == RESIZE_TERM ? IDC_RESIZETERM :
-                    cfg.resize_action == RESIZE_FONT ? IDC_RESIZEFONT :
-                    cfg.resize_action == RESIZE_EITHER ? IDC_RESIZEEITHER :
-                    IDC_RESIZENONE);
-    CheckRadioButton(hwnd, IDC_COEALWAYS, IDC_COENORMAL,
-                    cfg.close_on_exit == AUTO ? IDC_COENORMAL :
-                    cfg.close_on_exit ==
-                    FORCE_OFF ? IDC_COENEVER : IDC_COEALWAYS);
-    CheckDlgButton(hwnd, IDC_CLOSEWARN, cfg.warn_on_close);
-
-    SetDlgItemText(hwnd, IDC_TTEDIT, cfg.termtype);
-    SetDlgItemText(hwnd, IDC_TSEDIT, cfg.termspeed);
-    SetDlgItemText(hwnd, IDC_R_TSEDIT, cfg.termspeed);
-    SetDlgItemText(hwnd, IDC_RLLUSEREDIT, cfg.localusername);
-    SetDlgItemText(hwnd, IDC_LOGEDIT, cfg.username);
-    SetDlgItemText(hwnd, IDC_LGFEDIT, cfg.logfilename.path);
-    CheckRadioButton(hwnd, IDC_LSTATOFF, IDC_LSTATPACKET,
-                    cfg.logtype == LGTYP_NONE ? IDC_LSTATOFF :
-                    cfg.logtype == LGTYP_ASCII ? IDC_LSTATASCII :
-                    cfg.logtype == LGTYP_DEBUG ? IDC_LSTATRAW :
-                    IDC_LSTATPACKET);
-    CheckRadioButton(hwnd, IDC_LSTATXOVR, IDC_LSTATXASK,
-                    cfg.logxfovr == LGXF_OVR ? IDC_LSTATXOVR :
-                    cfg.logxfovr == LGXF_ASK ? IDC_LSTATXASK :
-                    IDC_LSTATXAPN);
-    {
-       char *p = cfg.environmt;
-       SendDlgItemMessage(hwnd, IDC_ENVLIST, LB_RESETCONTENT, 0, 0);
-       while (*p) {
-           SendDlgItemMessage(hwnd, IDC_ENVLIST, LB_ADDSTRING, 0,
-                              (LPARAM) p);
-           p += strlen(p) + 1;
-       }
-       p = cfg.portfwd;
-       while (*p) {
-           SendDlgItemMessage(hwnd, IDC_PFWDLIST, LB_ADDSTRING, 0,
-                              (LPARAM) p);
-           p += strlen(p) + 1;
-       }
-    }
-    CheckRadioButton(hwnd, IDC_EMBSD, IDC_EMRFC,
-                    cfg.rfc_environ ? IDC_EMRFC : IDC_EMBSD);
-    CheckRadioButton(hwnd, IDC_TPASSIVE, IDC_TACTIVE,
-                    cfg.passive_telnet ? IDC_TPASSIVE : IDC_TACTIVE);
-
-    SetDlgItemText(hwnd, IDC_TTEDIT, cfg.termtype);
-    SetDlgItemText(hwnd, IDC_LOGEDIT, cfg.username);
-    CheckDlgButton(hwnd, IDC_NOPTY, cfg.nopty);
-    CheckDlgButton(hwnd, IDC_COMPRESS, cfg.compression);
-    CheckDlgButton(hwnd, IDC_SSH2DES, cfg.ssh2_des_cbc);
-    CheckDlgButton(hwnd, IDC_AGENTFWD, cfg.agentfwd);
-    CheckDlgButton(hwnd, IDC_CHANGEUSER, cfg.change_username);
-    CheckRadioButton(hwnd, IDC_SSHPROT1ONLY, IDC_SSHPROT2ONLY,
-                    cfg.sshprot == 1 ? IDC_SSHPROT1 :
-                    cfg.sshprot == 2 ? IDC_SSHPROT2 :
-                    cfg.sshprot == 3 ? IDC_SSHPROT2ONLY : IDC_SSHPROT1ONLY);
-    CheckDlgButton(hwnd, IDC_AUTHTIS, cfg.try_tis_auth);
-    CheckDlgButton(hwnd, IDC_AUTHKI, cfg.try_ki_auth);
-    SetDlgItemText(hwnd, IDC_PKEDIT, cfg.keyfile.path);
-    SetDlgItemText(hwnd, IDC_CMDEDIT, cfg.remote_cmd);
-
-    {
-       int i;
-       static const struct { char *s; int c; } ciphers[] = {
-           { "3DES",                   CIPHER_3DES },
-           { "Blowfish",               CIPHER_BLOWFISH },
-           { "DES",                    CIPHER_DES },
-           { "AES (SSH 2 only)",       CIPHER_AES },
-           { "-- warn below here --",  CIPHER_WARN }
-       };
-
-       /* Set up the "selected ciphers" box. */
-       /* (cipherlist assumed to contain all ciphers) */
-       SendDlgItemMessage(hwnd, IDC_CIPHERLIST, LB_RESETCONTENT, 0, 0);
-       for (i = 0; i < CIPHER_MAX; i++) {
-           int c = cfg.ssh_cipherlist[i];
-           int j, pos;
-           char *cstr = NULL;
-           for (j = 0; j < (sizeof ciphers) / (sizeof ciphers[0]); j++) {
-               if (ciphers[j].c == c) {
-                   cstr = ciphers[j].s;
-                   break;
-               }
-           }
-           pos = SendDlgItemMessage(hwnd, IDC_CIPHERLIST, LB_ADDSTRING,
-                                    0, (LPARAM) cstr);
-           SendDlgItemMessage(hwnd, IDC_CIPHERLIST, LB_SETITEMDATA,
-                              pos, (LPARAM) c);
-       }
-
-    }
-
-    CheckRadioButton(hwnd, IDC_MBWINDOWS, IDC_MBXTERM,
-                    cfg.mouse_is_xterm ? IDC_MBXTERM : IDC_MBWINDOWS);
-    CheckRadioButton(hwnd, IDC_SELTYPELEX, IDC_SELTYPERECT,
-                    cfg.rect_select == 0 ? IDC_SELTYPELEX : IDC_SELTYPERECT);
-    CheckDlgButton(hwnd, IDC_MOUSEOVERRIDE, cfg.mouse_override);
-    CheckDlgButton(hwnd, IDC_RAWCNP, cfg.rawcnp);
-    CheckDlgButton(hwnd, IDC_RTFPASTE, cfg.rtf_paste);
-    {
-       static int tabs[4] = { 25, 61, 96, 128 };
-       SendDlgItemMessage(hwnd, IDC_CCLIST, LB_SETTABSTOPS, 4,
-                          (LPARAM) tabs);
-    }
-    for (i = 0; i < 128; i++) {
-       char str[100];
-       sprintf(str, "%d\t(0x%02X)\t%c\t%d", i, i,
-               (i >= 0x21 && i != 0x7F) ? i : ' ', cfg.wordness[i]);
-       SendDlgItemMessage(hwnd, IDC_CCLIST, LB_ADDSTRING, 0,
-                          (LPARAM) str);
-    }
-
-    CheckDlgButton(hwnd, IDC_BOLDCOLOUR, cfg.bold_colour);
-    CheckDlgButton(hwnd, IDC_PALETTE, cfg.try_palette);
-    {
-       int i, n;
-       n = SendDlgItemMessage(hwnd, IDC_COLOURLIST, LB_GETCOUNT, 0, 0);
-       for (i = n; i-- > 0;)
-           SendDlgItemMessage(hwnd, IDC_COLOURLIST,
-                              LB_DELETESTRING, i, 0);
-       for (i = 0; i < 22; i++)
-           SendDlgItemMessage(hwnd, IDC_COLOURLIST, LB_ADDSTRING, 0,
-                              (LPARAM) colours[i]);
-    }
-    SendDlgItemMessage(hwnd, IDC_COLOURLIST, LB_SETCURSEL, 0, 0);
-    SetDlgItemInt(hwnd, IDC_RVALUE, cfg.colours[0][0], FALSE);
-    SetDlgItemInt(hwnd, IDC_GVALUE, cfg.colours[0][1], FALSE);
-    SetDlgItemInt(hwnd, IDC_BVALUE, cfg.colours[0][2], FALSE);
-
-    {
-       int i;
-       char *cp;
-       strcpy(cfg.line_codepage, cp_name(decode_codepage(cfg.line_codepage)));
-       SendDlgItemMessage(hwnd, IDC_CODEPAGE, CB_RESETCONTENT, 0, 0);
-       CheckDlgButton (hwnd, IDC_CAPSLOCKCYR, cfg.xlat_capslockcyr);
-       for (i = 0; (cp = cp_enumerate(i)) != NULL; i++) {
-           SendDlgItemMessage(hwnd, IDC_CODEPAGE, CB_ADDSTRING,
-                              0, (LPARAM) cp);
-       }
-       SetDlgItemText(hwnd, IDC_CODEPAGE, cfg.line_codepage);
-    }
-
-    {
-       int i, nprinters;
-       printer_enum *pe;
-       pe = printer_start_enum(&nprinters);
-       SendDlgItemMessage(hwnd, IDC_PRINTER, CB_RESETCONTENT, 0, 0);
-       SendDlgItemMessage(hwnd, IDC_PRINTER, CB_ADDSTRING,
-                          0, (LPARAM) PRINTER_DISABLED_STRING);
-       for (i = 0; i < nprinters; i++) {
-           char *printer_name = printer_get_name(pe, i);
-           SendDlgItemMessage(hwnd, IDC_PRINTER, CB_ADDSTRING,
-                              0, (LPARAM) printer_name);
-       }
-       printer_finish_enum(pe);
-       SetDlgItemText(hwnd, IDC_PRINTER,
-                      *cfg.printer ? cfg.printer : PRINTER_DISABLED_STRING);
-    }
-
-    CheckRadioButton(hwnd, IDC_VTXWINDOWS, IDC_VTUNICODE,
-                    cfg.vtmode == VT_XWINDOWS ? IDC_VTXWINDOWS :
-                    cfg.vtmode == VT_OEMANSI ? IDC_VTOEMANSI :
-                    cfg.vtmode == VT_OEMONLY ? IDC_VTOEMONLY :
-                    cfg.vtmode == VT_UNICODE ? IDC_VTUNICODE :
-                    IDC_VTPOORMAN);
-
-    CheckDlgButton(hwnd, IDC_X11_FORWARD, cfg.x11_forward);
-    SetDlgItemText(hwnd, IDC_X11_DISPLAY, cfg.x11_display);
-    CheckRadioButton(hwnd, IDC_X11MIT, IDC_X11XDM,
-                    cfg.x11_auth == X11_MIT ? IDC_X11MIT : IDC_X11XDM);
-
-    CheckDlgButton(hwnd, IDC_LPORT_ALL, cfg.lport_acceptall);
-    CheckDlgButton(hwnd, IDC_RPORT_ALL, cfg.rport_acceptall);
-    CheckRadioButton(hwnd, IDC_PFWDLOCAL, IDC_PFWDREMOTE, IDC_PFWDLOCAL);
-
-    /* proxy config */
-    CheckRadioButton(hwnd, IDC_PROXYTYPENONE, IDC_PROXYTYPETELNET,
-                    cfg.proxy_type == PROXY_HTTP ? IDC_PROXYTYPEHTTP :
-                    cfg.proxy_type == PROXY_SOCKS ? IDC_PROXYTYPESOCKS :
-                    cfg.proxy_type == PROXY_TELNET ? IDC_PROXYTYPETELNET : IDC_PROXYTYPENONE);
-    SetDlgItemText(hwnd, IDC_PROXYHOSTEDIT, cfg.proxy_host);
-    SetDlgItemInt(hwnd, IDC_PROXYPORTEDIT, cfg.proxy_port, FALSE);
-    SetDlgItemText(hwnd, IDC_PROXYEXCLUDEEDIT, cfg.proxy_exclude_list);
-    CheckDlgButton(hwnd, IDC_PROXYLOCALHOST, cfg.even_proxy_localhost);
-    CheckRadioButton(hwnd, IDC_PROXYDNSNO, IDC_PROXYDNSYES,
-                    cfg.proxy_dns == FORCE_OFF ? IDC_PROXYDNSNO :
-                    cfg.proxy_dns == FORCE_ON ? IDC_PROXYDNSYES :
-                    IDC_PROXYDNSAUTO);
-    SetDlgItemText(hwnd, IDC_PROXYTELNETCMDEDIT, cfg.proxy_telnet_command);
-    SetDlgItemText(hwnd, IDC_PROXYUSEREDIT, cfg.proxy_username);
-    SetDlgItemText(hwnd, IDC_PROXYPASSEDIT, cfg.proxy_password);
-    CheckRadioButton(hwnd, IDC_PROXYSOCKSVER5, IDC_PROXYSOCKSVER4,
-                    cfg.proxy_socks_version == 4 ? IDC_PROXYSOCKSVER4 : IDC_PROXYSOCKSVER5);
-
-    /* SSH bugs config */
-    SendDlgItemMessage(hwnd, IDC_BUGD_IGNORE1, CB_RESETCONTENT, 0, 0);
-    SendDlgItemMessage(hwnd, IDC_BUGD_IGNORE1, CB_ADDSTRING, 0, (LPARAM)"Auto");
-    SendDlgItemMessage(hwnd, IDC_BUGD_IGNORE1, CB_ADDSTRING, 0, (LPARAM)"Off");
-    SendDlgItemMessage(hwnd, IDC_BUGD_IGNORE1, CB_ADDSTRING, 0, (LPARAM)"On");
-    SendDlgItemMessage(hwnd, IDC_BUGD_IGNORE1, CB_SETCURSEL,
-                      cfg.sshbug_ignore1 == FORCE_ON ? 2 :
-                      cfg.sshbug_ignore1 == FORCE_OFF ? 1 : 0, 0);
-    SendDlgItemMessage(hwnd, IDC_BUGD_PLAINPW1, CB_RESETCONTENT, 0, 0);
-    SendDlgItemMessage(hwnd, IDC_BUGD_PLAINPW1, CB_ADDSTRING, 0, (LPARAM)"Auto");
-    SendDlgItemMessage(hwnd, IDC_BUGD_PLAINPW1, CB_ADDSTRING, 0, (LPARAM)"Off");
-    SendDlgItemMessage(hwnd, IDC_BUGD_PLAINPW1, CB_ADDSTRING, 0, (LPARAM)"On");
-    SendDlgItemMessage(hwnd, IDC_BUGD_PLAINPW1, CB_SETCURSEL,
-                      cfg.sshbug_plainpw1 == FORCE_ON ? 2 :
-                      cfg.sshbug_plainpw1 == FORCE_OFF ? 1 : 0, 0);
-    SendDlgItemMessage(hwnd, IDC_BUGD_RSA1, CB_RESETCONTENT, 0, 0);
-    SendDlgItemMessage(hwnd, IDC_BUGD_RSA1, CB_ADDSTRING, 0, (LPARAM)"Auto");
-    SendDlgItemMessage(hwnd, IDC_BUGD_RSA1, CB_ADDSTRING, 0, (LPARAM)"Off");
-    SendDlgItemMessage(hwnd, IDC_BUGD_RSA1, CB_ADDSTRING, 0, (LPARAM)"On");
-    SendDlgItemMessage(hwnd, IDC_BUGD_RSA1, CB_SETCURSEL,
-                      cfg.sshbug_rsa1 == FORCE_ON ? 2 :
-                      cfg.sshbug_rsa1 == FORCE_OFF ? 1 : 0, 0);
-    SendDlgItemMessage(hwnd, IDC_BUGD_HMAC2, CB_RESETCONTENT, 0, 0);
-    SendDlgItemMessage(hwnd, IDC_BUGD_HMAC2, CB_ADDSTRING, 0, (LPARAM)"Auto");
-    SendDlgItemMessage(hwnd, IDC_BUGD_HMAC2, CB_ADDSTRING, 0, (LPARAM)"Off");
-    SendDlgItemMessage(hwnd, IDC_BUGD_HMAC2, CB_ADDSTRING, 0, (LPARAM)"On");
-    SendDlgItemMessage(hwnd, IDC_BUGD_HMAC2, CB_SETCURSEL,
-                      cfg.sshbug_hmac2 == FORCE_ON ? 2 :
-                      cfg.sshbug_hmac2 == FORCE_OFF ? 1 : 0, 0);
-    SendDlgItemMessage(hwnd, IDC_BUGD_DERIVEKEY2, CB_RESETCONTENT, 0, 0);
-    SendDlgItemMessage(hwnd, IDC_BUGD_DERIVEKEY2, CB_ADDSTRING, 0, (LPARAM)"Auto");
-    SendDlgItemMessage(hwnd, IDC_BUGD_DERIVEKEY2, CB_ADDSTRING, 0, (LPARAM)"Off");
-    SendDlgItemMessage(hwnd, IDC_BUGD_DERIVEKEY2, CB_ADDSTRING, 0, (LPARAM)"On");
-    SendDlgItemMessage(hwnd, IDC_BUGD_DERIVEKEY2, CB_SETCURSEL,
-                      cfg.sshbug_derivekey2 == FORCE_ON ? 2 :
-                      cfg.sshbug_derivekey2 == FORCE_OFF ? 1 : 0, 0);
-    SendDlgItemMessage(hwnd, IDC_BUGD_RSAPAD2, CB_RESETCONTENT, 0, 0);
-    SendDlgItemMessage(hwnd, IDC_BUGD_RSAPAD2, CB_ADDSTRING, 0, (LPARAM)"Auto");
-    SendDlgItemMessage(hwnd, IDC_BUGD_RSAPAD2, CB_ADDSTRING, 0, (LPARAM)"Off");
-    SendDlgItemMessage(hwnd, IDC_BUGD_RSAPAD2, CB_ADDSTRING, 0, (LPARAM)"On");
-    SendDlgItemMessage(hwnd, IDC_BUGD_RSAPAD2, CB_SETCURSEL,
-                      cfg.sshbug_rsapad2 == FORCE_ON ? 2 :
-                      cfg.sshbug_rsapad2 == FORCE_OFF ? 1 : 0, 0);
-    SendDlgItemMessage(hwnd, IDC_BUGD_DHGEX2, CB_RESETCONTENT, 0, 0);
-    SendDlgItemMessage(hwnd, IDC_BUGD_DHGEX2, CB_ADDSTRING, 0, (LPARAM)"Auto");
-    SendDlgItemMessage(hwnd, IDC_BUGD_DHGEX2, CB_ADDSTRING, 0, (LPARAM)"Off");
-    SendDlgItemMessage(hwnd, IDC_BUGD_DHGEX2, CB_ADDSTRING, 0, (LPARAM)"On");
-    SendDlgItemMessage(hwnd, IDC_BUGD_DHGEX2, CB_SETCURSEL,
-                      cfg.sshbug_dhgex2 == FORCE_ON ? 2 :
-                      cfg.sshbug_dhgex2 == FORCE_OFF ? 1 : 0, 0);
-    SendDlgItemMessage(hwnd, IDC_BUGD_PKSESSID2, CB_RESETCONTENT, 0, 0);
-    SendDlgItemMessage(hwnd, IDC_BUGD_PKSESSID2, CB_ADDSTRING, 0, (LPARAM)"Auto");
-    SendDlgItemMessage(hwnd, IDC_BUGD_PKSESSID2, CB_ADDSTRING, 0, (LPARAM)"Off");
-    SendDlgItemMessage(hwnd, IDC_BUGD_PKSESSID2, CB_ADDSTRING, 0, (LPARAM)"On");
-    SendDlgItemMessage(hwnd, IDC_BUGD_PKSESSID2, CB_SETCURSEL,
-                      cfg.sshbug_pksessid2 == FORCE_ON ? 2 :
-                      cfg.sshbug_pksessid2 == FORCE_OFF ? 1 : 0, 0);
-}
-
 struct treeview_faff {
     HWND treeview;
     HTREEITEM lastat[4];
 };
 
 static HTREEITEM treeview_insert(struct treeview_faff *faff,
-                                int level, char *text)
+                                int level, char *text, char *path)
 {
     TVINSERTSTRUCT ins;
     int i;
@@ -1458,8 +247,10 @@ static HTREEITEM treeview_insert(struct treeview_faff *faff,
 #else
 #define INSITEM item
 #endif
-    ins.INSITEM.mask = TVIF_TEXT;
+    ins.INSITEM.mask = TVIF_TEXT | TVIF_PARAM;
     ins.INSITEM.pszText = text;
+    ins.INSITEM.cchTextMax = strlen(text)+1;
+    ins.INSITEM.lParam = (LPARAM)path;
     newitem = TreeView_InsertItem(faff->treeview, &ins);
     if (level > 0)
        TreeView_Expand(faff->treeview, faff->lastat[level - 1],
@@ -1473,675 +264,50 @@ static HTREEITEM treeview_insert(struct treeview_faff *faff,
 /*
  * Create the panelfuls of controls in the configuration box.
  */
-static void create_controls(HWND hwnd, int dlgtype, int panel)
+static void create_controls(HWND hwnd, char *path)
 {
-    if (panel == sessionpanelstart) {
-       /* The Session panel. Accelerators used: [acgoh] nprtis elvd w */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       bartitle(&cp, "Basic options for your PuTTY session",
-                IDC_TITLE_SESSION);
-       if (dlgtype == 0) {
-           beginbox(&cp, "Specify your connection by host name or IP address",
-                    IDC_BOX_SESSION1);
-           multiedit(&cp,
-                     "Host &Name (or IP address)",
-                     IDC_HOSTSTATIC, IDC_HOST, 75,
-                     "&Port", IDC_PORTSTATIC, IDC_PORT, 25, NULL);
-           if (backends[3].backend == NULL) {
-               /* this is PuTTYtel, so only three protocols available */
-               radioline(&cp, "Protocol:", IDC_PROTSTATIC, 3,
-                         "&Raw", IDC_PROTRAW,
-                         "&Telnet", IDC_PROTTELNET,
-                         "Rlog&in", IDC_PROTRLOGIN, NULL);
-           } else {
-               radioline(&cp, "Protocol:", IDC_PROTSTATIC, 4,
-                         "&Raw", IDC_PROTRAW,
-                         "&Telnet", IDC_PROTTELNET,
-                         "Rlog&in", IDC_PROTRLOGIN,
-#ifdef FWHACK
-                         "&SSH/hack",
-#else
-                         "&SSH",
-#endif
-                         IDC_PROTSSH, NULL);
-           }
-           endbox(&cp);
-           beginbox(&cp, "Load, save or delete a stored session",
-                    IDC_BOX_SESSION2);
-           sesssaver(&cp, "Sav&ed Sessions",
-                     IDC_SESSSTATIC, IDC_SESSEDIT, IDC_SESSLIST,
-                     "&Load", IDC_SESSLOAD,
-                     "Sa&ve", IDC_SESSSAVE, "&Delete", IDC_SESSDEL, NULL);
-           endbox(&cp);
-       }
-       beginbox(&cp, NULL, IDC_BOX_SESSION3);
-       radioline(&cp, "Close &window on exit:", IDC_CLOSEEXIT, 4,
-                 "Always", IDC_COEALWAYS,
-                 "Never", IDC_COENEVER,
-                 "Only on clean exit", IDC_COENORMAL, NULL);
-       endbox(&cp);
-    }
+    struct ctlpos cp;
+    int index;
+    int base_id;
+    struct winctrls *wc;
 
-    if (panel == loggingpanelstart) {
-       /* The Logging panel. Accelerators used: [acgoh] tplsfwe */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       bartitle(&cp, "Options controlling session logging",
-                IDC_TITLE_LOGGING);
-       beginbox(&cp, NULL, IDC_BOX_LOGGING1);
-       radiobig(&cp,
-                "Session logging:", IDC_LSTATSTATIC,
-                "Logging &turned off completely", IDC_LSTATOFF,
-                "Log &printable output only", IDC_LSTATASCII,
-                "&Log all session output", IDC_LSTATRAW,
-                "Log &SSH packet data", IDC_LSTATPACKET,
-                NULL);
-       editbutton(&cp, "Log &file name:",
-                  IDC_LGFSTATIC, IDC_LGFEDIT, "Bro&wse...",
-                  IDC_LGFBUTTON);
-       statictext(&cp, "(Log file name can contain &&Y, &&M, &&D for date,"
-                  " &&T for time, and &&H for host name)", 2, IDC_LGFEXPLAIN);
-       radiobig(&cp,
-                "What to do if the log file already &exists:",
-                IDC_LSTATXIST, "Always overwrite it", IDC_LSTATXOVR,
-                "Always append to the end of it", IDC_LSTATXAPN,
-                "Ask the user every time", IDC_LSTATXASK, NULL);
-       endbox(&cp);
-    }
-
-    if (panel == terminalpanelstart) {
-       /* The Terminal panel. Accelerators used: [acgoh] wdren lts p */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       bartitle(&cp, "Options controlling the terminal emulation",
-                IDC_TITLE_TERMINAL);
-       beginbox(&cp, "Set various terminal options", IDC_BOX_TERMINAL1);
-       checkbox(&cp, "Auto &wrap mode initially on", IDC_WRAPMODE);
-       checkbox(&cp, "&DEC Origin Mode initially on", IDC_DECOM);
-       checkbox(&cp, "Implicit C&R in every LF", IDC_LFHASCR);
-       checkbox(&cp, "Use background colour to &erase screen", IDC_BCE);
-       checkbox(&cp, "Enable bli&nking text", IDC_BLINKTEXT);
-       multiedit(&cp,
-                 "An&swerback to ^E:", IDC_ANSWERBACK,
-                 IDC_ANSWEREDIT, 100, NULL);
-       endbox(&cp);
-
-       beginbox(&cp, "Line discipline options", IDC_BOX_TERMINAL2);
-       radioline(&cp, "&Local echo:", IDC_ECHOSTATIC, 3,
-                 "Auto", IDC_ECHOBACKEND,
-                 "Force on", IDC_ECHOYES, "Force off", IDC_ECHONO, NULL);
-       radioline(&cp, "Local line edi&ting:", IDC_EDITSTATIC, 3,
-                 "Auto", IDC_EDITBACKEND,
-                 "Force on", IDC_EDITYES, "Force off", IDC_EDITNO, NULL);
-       endbox(&cp);
-
-       beginbox(&cp, "Remote-controlled printing", IDC_BOX_TERMINAL3);
-       combobox(&cp, "&Printer to send ANSI printer output to:",
-                IDC_PRINTERSTATIC, IDC_PRINTER);
-       endbox(&cp);
-    }
-
-    if (panel == featurespanelstart) {
-       /* The Features panel. Accelerators used: [acgoh] ukswtbrx */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       bartitle(&cp, "Enabling and disabling advanced terminal features ",
-                IDC_TITLE_FEATURES);
-       beginbox(&cp, NULL, IDC_BOX_FEATURES1);
-       checkbox(&cp, "Disable application c&ursor keys mode", IDC_NOAPPLICC);
-       checkbox(&cp, "Disable application &keypad mode", IDC_NOAPPLICK);
-       checkbox(&cp, "Disable &xterm-style mouse reporting", IDC_NOMOUSEREP);
-       checkbox(&cp, "Disable remote-controlled terminal re&sizing",
-                IDC_NORESIZE);
-       checkbox(&cp, "Disable s&witching to alternate terminal screen",
-                IDC_NOALTSCREEN);
-       checkbox(&cp, "Disable remote-controlled window &title changing",
-                IDC_NOWINTITLE);
-       checkbox(&cp, "Disable destructive &backspace on server sending ^?",
-                IDC_NODBACKSPACE);
-       checkbox(&cp, "Disable remote-controlled cha&racter set configuration",
-                IDC_NOCHARSET);
-       endbox(&cp);
-    }
-
-    if (panel == bellpanelstart) {
-       /* The Bell panel. Accelerators used: [acgoh] bdsm wit */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       bartitle(&cp, "Options controlling the terminal bell",
-                IDC_TITLE_BELL);
-       beginbox(&cp, "Set the style of bell", IDC_BOX_BELL1);
-       radiobig(&cp,
-                "Action to happen when a &bell occurs:", IDC_BELLSTATIC,
-                "None (bell disabled)", IDC_BELL_DISABLED,
-                "Play Windows Default Sound", IDC_BELL_DEFAULT,
-                "Play a custom sound file", IDC_BELL_WAVEFILE,
-                "Visual bell (flash window)", IDC_BELL_VISUAL, NULL);
-       editbutton(&cp, "Custom sound file to play as a bell:",
-                  IDC_BELL_WAVESTATIC, IDC_BELL_WAVEEDIT,
-                  "Bro&wse...", IDC_BELL_WAVEBROWSE);
-       radioline(&cp, "Taskbar/caption &indication on bell:",
-                 IDC_B_IND_STATIC, 3, "Disabled", IDC_B_IND_DISABLED,
-                 "Flashing", IDC_B_IND_FLASH, "Steady", IDC_B_IND_STEADY,
-                 NULL);
-       endbox(&cp);
-       beginbox(&cp, "Control the bell overload behaviour",
-                IDC_BOX_BELL2);
-       checkbox(&cp, "Bell is temporarily &disabled when over-used",
-                IDC_BELLOVL);
-       staticedit(&cp, "Over-use means this &many bells...",
-                  IDC_BELLOVLNSTATIC, IDC_BELLOVLN, 20);
-       staticedit(&cp, "... in &this many seconds",
-                  IDC_BELLOVLTSTATIC, IDC_BELLOVLT, 20);
-       statictext(&cp,
-                  "The bell is re-enabled after a few seconds of silence.",
-                  1, IDC_BELLOVLEXPLAIN);
-       staticedit(&cp, "Seconds of &silence required", IDC_BELLOVLSSTATIC,
-                  IDC_BELLOVLS, 20);
-       endbox(&cp);
-    }
-
-    if (panel == keyboardpanelstart) {
-       /* The Keyboard panel. Accelerators used: [acgoh] bef rntd */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       bartitle(&cp, "Options controlling the effects of keys",
-                IDC_TITLE_KEYBOARD);
-       beginbox(&cp, "Change the sequences sent by:", IDC_BOX_KEYBOARD1);
-       radioline(&cp, "The &Backspace key", IDC_DELSTATIC, 2,
-                 "Control-H", IDC_DEL008,
-                 "Control-? (127)", IDC_DEL127, NULL);
-       radioline(&cp, "The Home and &End keys", IDC_HOMESTATIC, 2,
-                 "Standard", IDC_HOMETILDE, "rxvt", IDC_HOMERXVT, NULL);
-       radioline(&cp, "The &Function keys and keypad", IDC_FUNCSTATIC, 3,
-                 "ESC[n~", IDC_FUNCTILDE,
-                 "Linux", IDC_FUNCLINUX,
-                 "Xterm R6", IDC_FUNCXTERM,
-                 "VT400", IDC_FUNCVT400,
-                 "VT100+", IDC_FUNCVT100P, "SCO", IDC_FUNCSCO, NULL);
-       endbox(&cp);
-       beginbox(&cp, "Application keypad settings:", IDC_BOX_KEYBOARD2);
-       radioline(&cp, "Initial state of cu&rsor keys:", IDC_CURSTATIC, 2,
-                 "Normal", IDC_CURNORMAL,
-                 "Application", IDC_CURAPPLIC, NULL);
-       radioline(&cp, "Initial state of &numeric keypad:", IDC_KPSTATIC,
-                 3, "Normal", IDC_KPNORMAL, "Application", IDC_KPAPPLIC,
-                 "NetHack", IDC_KPNH, NULL);
-       endbox(&cp);
-       beginbox(&cp, "Enable extra keyboard features:",
-                IDC_BOX_KEYBOARD3);
-       checkbox(&cp, "AltGr ac&ts as Compose key", IDC_COMPOSEKEY);
-       checkbox(&cp, "Control-Alt is &different from AltGr",
-                IDC_CTRLALTKEYS);
-       endbox(&cp);
-    }
-
-    if (panel == windowpanelstart) {
-       /* The Window panel. Accelerators used: [acgoh] rmz sdikp */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       bartitle(&cp, "Options controlling PuTTY's window",
-                IDC_TITLE_WINDOW);
-       beginbox(&cp, "Set the size of the window", IDC_BOX_WINDOW1);
-       multiedit(&cp,
-                 "&Rows", IDC_ROWSSTATIC, IDC_ROWSEDIT, 50,
-                 "Colu&mns", IDC_COLSSTATIC, IDC_COLSEDIT, 50, NULL);
-       radiobig(&cp, "When window is resi&zed:", IDC_RESIZESTATIC,
-                "Change the number of rows and columns", IDC_RESIZETERM,
-                "Change the size of the font", IDC_RESIZEFONT,
-                "Change font size only when maximised", IDC_RESIZEEITHER,
-                "Forbid resizing completely", IDC_RESIZENONE, NULL);
-       endbox(&cp);
-       beginbox(&cp, "Control the scrollback in the window",
-                IDC_BOX_WINDOW2);
-       staticedit(&cp, "Lines of &scrollback",
-                  IDC_SAVESTATIC, IDC_SAVEEDIT, 50);
-       checkbox(&cp, "&Display scrollbar", IDC_SCROLLBAR);
-       checkbox(&cp, "D&isplay scrollbar in full screen mode", IDC_SCROLLBARFULLSCREEN);
-       checkbox(&cp, "Reset scrollback on &keypress", IDC_SCROLLKEY);
-       checkbox(&cp, "Reset scrollback on dis&play activity",
-                IDC_SCROLLDISP);
-       endbox(&cp);
-    }
-
-    if (panel == appearancepanelstart) {
-       /* The Appearance panel. Accelerators used: [acgoh] luvb n ti p s */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       bartitle(&cp, "Configure the appearance of PuTTY's window",
-                IDC_TITLE_APPEARANCE);
-       beginbox(&cp, "Adjust the use of the cursor", IDC_BOX_APPEARANCE1);
-       radioline(&cp, "Cursor appearance:", IDC_CURSORSTATIC, 3,
-                 "B&lock", IDC_CURBLOCK,
-                 "&Underline", IDC_CURUNDER,
-                 "&Vertical line", IDC_CURVERT, NULL);
-       checkbox(&cp, "Cursor &blinks", IDC_BLINKCUR);
-       endbox(&cp);
-       beginbox(&cp, "Set the font used in the terminal window",
-                IDC_BOX_APPEARANCE2);
-       staticbtn(&cp, "", IDC_FONTSTATIC, "Cha&nge...", IDC_CHOOSEFONT);
-       endbox(&cp);
-       beginbox(&cp, "Adjust the use of the window title",
-                IDC_BOX_APPEARANCE3);
-       multiedit(&cp,
-                 "Window &title:", IDC_WINTITLE, IDC_WINEDIT, 100, NULL);
-       checkbox(&cp, "Separate window and &icon titles", IDC_WINNAME);
-       endbox(&cp);
-       beginbox(&cp, "Adjust the use of the mouse pointer",
-                IDC_BOX_APPEARANCE4);
-       checkbox(&cp, "Hide mouse &pointer when typing in window",
-                IDC_HIDEMOUSE);
-       endbox(&cp);
-       beginbox(&cp, "Adjust the window border", IDC_BOX_APPEARANCE5);
-       checkbox(&cp, "&Sunken-edge border (slightly thicker)",
-                IDC_SUNKENEDGE);
-       staticedit(&cp, "Gap between text and window edge",
-                  IDC_WINBSTATIC, IDC_WINBEDIT, 20);
-       endbox(&cp);
-    }
-
-    if (panel == behaviourpanelstart) {
-       /* The Behaviour panel. Accelerators used: [acgoh] w4yltf */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       bartitle(&cp, "Configure the behaviour of PuTTY's window",
-                IDC_TITLE_WINDOW);
-       beginbox(&cp, NULL, IDC_BOX_BEHAVIOUR1);
-       checkbox(&cp, "&Warn before closing window", IDC_CLOSEWARN);
-       checkbox(&cp, "Window closes on ALT-F&4", IDC_ALTF4);
-       checkbox(&cp, "S&ystem menu appears on ALT-Space", IDC_ALTSPACE);
-       checkbox(&cp, "System menu appears on A&LT alone", IDC_ALTONLY);
-       checkbox(&cp, "Ensure window is always on &top", IDC_ALWAYSONTOP);
-       checkbox(&cp, "&Full screen on Alt-Enter", IDC_FULLSCREENONALTENTER);
-       endbox(&cp);
-    }
-
-    if (panel == translationpanelstart) {
-       /* The Translation panel. Accelerators used: [acgoh] rxbepus */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       bartitle(&cp, "Options controlling character set translation",
-                IDC_TITLE_TRANSLATION);
-       beginbox(&cp, "Character set translation on received data",
-                IDC_BOX_TRANSLATION1);
-       combobox(&cp, "&Received data assumed to be in which character set:",
-                IDC_CODEPAGESTATIC, IDC_CODEPAGE);
-       endbox(&cp);
-        beginbox(&cp, "Enable character set translation on input data",
-                 IDC_BOX_TRANSLATION2);
-        checkbox(&cp, "Cap&s Lock acts as Cyrillic switch",
-                 IDC_CAPSLOCKCYR);
-        endbox(&cp);
-       beginbox(&cp, "Adjust how PuTTY displays line drawing characters",
-                IDC_BOX_TRANSLATION3);
-       radiobig(&cp,
-                "Handling of line drawing characters:", IDC_VTSTATIC,
-                "Font has &XWindows encoding", IDC_VTXWINDOWS,
-                "Use font in &both ANSI and OEM modes", IDC_VTOEMANSI,
-                "Use font in O&EM mode only", IDC_VTOEMONLY,
-                "&Poor man's line drawing (" "+" ", " "-" " and " "|" ")",
-                IDC_VTPOORMAN, "&Unicode mode", IDC_VTUNICODE, NULL);
-       endbox(&cp);
-    }
-
-    if (panel == selectionpanelstart) {
-       /* The Selection panel. Accelerators used: [acgoh] df wxp est nr */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       bartitle(&cp, "Options controlling copy and paste",
-                IDC_TITLE_SELECTION);
-       beginbox(&cp, "Translation of pasted characters",
-                IDC_BOX_SELECTION1);
-       checkbox(&cp,
-                "&Don't translate line drawing chars into +, - and |",
-                IDC_RAWCNP);
-       checkbox(&cp,
-                "Paste to clipboard in RT&F as well as plain text",
-                IDC_RTFPASTE);
-       endbox(&cp);
-       beginbox(&cp, "Control which mouse button does which thing",
-                IDC_BOX_SELECTION2);
-       radiobig(&cp, "Action of mouse buttons:", IDC_MBSTATIC,
-                "&Windows (Right pastes, Middle extends)", IDC_MBWINDOWS,
-                "&xterm (Right extends, Middle pastes)", IDC_MBXTERM,
-                NULL);
-       checkbox(&cp,
-                "Shift overrides a&pplication's use of mouse",
-                IDC_MOUSEOVERRIDE);
-        radioline(&cp,
-                  "Default selection mode (Alt+drag does the other one):",
-                  IDC_SELTYPESTATIC, 2,
-                 "&Normal", IDC_SELTYPELEX,
-                 "&Rectangular block", IDC_SELTYPERECT, NULL);
-       endbox(&cp);
-       beginbox(&cp, "Control the select-one-word-at-a-time mode",
-                IDC_BOX_SELECTION3);
-       charclass(&cp, "Charact&er classes:", IDC_CCSTATIC, IDC_CCLIST,
-                 "&Set", IDC_CCSET, IDC_CCEDIT,
-                 "&to class", IDC_CCSTATIC2);
-       endbox(&cp);
-    }
-
-    if (panel == colourspanelstart) {
-       /* The Colours panel. Accelerators used: [acgoh] blum */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       bartitle(&cp, "Options controlling use of colours",
-                IDC_TITLE_COLOURS);
-       beginbox(&cp, "General options for colour usage",
-                IDC_BOX_COLOURS1);
-       checkbox(&cp, "&Bolded text is a different colour",
-                IDC_BOLDCOLOUR);
-       checkbox(&cp, "Attempt to use &logical palettes", IDC_PALETTE);
-       endbox(&cp);
-       beginbox(&cp, "Adjust the precise colours PuTTY displays",
-                IDC_BOX_COLOURS2);
-       colouredit(&cp, "Select a colo&ur and then click to modify it:",
-                  IDC_COLOURSTATIC, IDC_COLOURLIST,
-                  "&Modify...", IDC_CHANGE,
-                  "Red:", IDC_RSTATIC, IDC_RVALUE,
-                  "Green:", IDC_GSTATIC, IDC_GVALUE,
-                  "Blue:", IDC_BSTATIC, IDC_BVALUE, NULL);
-       endbox(&cp);
-    }
-
-    if (panel == connectionpanelstart) {
-       /* The Connection panel. Accelerators used: [acgoh] tukn */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       bartitle(&cp, "Options controlling the connection",
-                IDC_TITLE_CONNECTION);
-       if (dlgtype == 0) {
-           beginbox(&cp, "Data to send to the server",
-                    IDC_BOX_CONNECTION1);
-           staticedit(&cp, "Terminal-&type string", IDC_TTSTATIC,
-                      IDC_TTEDIT, 50);
-           staticedit(&cp, "Auto-login &username", IDC_LOGSTATIC,
-                      IDC_LOGEDIT, 50);
-           endbox(&cp);
-       } else {
-           beginbox(&cp, "Adjust telnet session.", IDC_BOX_CONNECTION1);
-           checkbox(&cp, "Keyboard sends telnet Backspace and Interrupt",
-                    IDC_TELNETKEY);
-           checkbox(&cp, "Return key sends telnet New Line instead of ^M",
-                    IDC_TELNETRET);
-           endbox(&cp);
-       }
-       beginbox(&cp, "Sending of null packets to keep session active",
-                IDC_BOX_CONNECTION2);
-       staticedit(&cp, "Seconds between &keepalives (0 to turn off)",
-                  IDC_PINGSTATIC, IDC_PINGEDIT, 20);
-       endbox(&cp);
-       if (dlgtype == 0) {
-           beginbox(&cp, "Low-level TCP connection options",
-                    IDC_BOX_CONNECTION3);
-           checkbox(&cp, "Disable &Nagle's algorithm (TCP_NODELAY option)",
-                    IDC_NODELAY);
-           endbox(&cp);
-       }
-    }
-
-    if (panel == proxypanelstart) {
-       /* The Proxy panel. Accelerators used: [acgoh] ntslypeuwmvxd */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       if (dlgtype == 0) {
-           bartitle(&cp, "Options controlling proxy usage",
-                    IDC_TITLE_PROXY);
-           beginbox(&cp, "Proxy basics", IDC_BOX_PROXY1);
-           radioline(&cp, "Proxy type:", IDC_PROXYTYPESTATIC, 4,
-                     "&None", IDC_PROXYTYPENONE,
-                     "H&TTP", IDC_PROXYTYPEHTTP,
-                     "&SOCKS", IDC_PROXYTYPESOCKS,
-                     "Te&lnet", IDC_PROXYTYPETELNET, NULL);
-           multiedit(&cp,
-                     "Prox&y Host", IDC_PROXYHOSTSTATIC, IDC_PROXYHOSTEDIT, 80,
-                     "&Port", IDC_PROXYPORTSTATIC, IDC_PROXYPORTEDIT, 20, NULL);
-           multiedit(&cp,
-                     "&Exclude Hosts/IPs", IDC_PROXYEXCLUDESTATIC,
-                     IDC_PROXYEXCLUDEEDIT, 100, NULL);
-           checkbox(&cp, "Consider pro&xying local host connections",
-                    IDC_PROXYLOCALHOST);
-           radioline(&cp, "Do &DNS name lookup at proxy end:",
-                     IDC_PROXYDNSSTATIC, 3,
-                     "No", IDC_PROXYDNSNO,
-                     "Auto", IDC_PROXYDNSAUTO,
-                     "Yes", IDC_PROXYDNSYES, NULL);
-           staticedit(&cp, "&Username", IDC_PROXYUSERSTATIC,
-                      IDC_PROXYUSEREDIT, 60);
-           staticpassedit(&cp, "Pass&word", IDC_PROXYPASSSTATIC,
-                          IDC_PROXYPASSEDIT, 60);
-           endbox(&cp);
-           beginbox(&cp, "Misc. proxy settings", IDC_BOX_PROXY2);
-           multiedit(&cp,
-                     "Telnet co&mmand", IDC_PROXYTELNETCMDSTATIC,
-                     IDC_PROXYTELNETCMDEDIT, 100, NULL);
-           radioline(&cp, "SOCKS &Version", IDC_PROXYSOCKSVERSTATIC,
-                     2, "Version 5", IDC_PROXYSOCKSVER5, "Version 4",
-                     IDC_PROXYSOCKSVER4, NULL);
-           endbox(&cp);
-       }
-    }
-
-    if (panel == telnetpanelstart) {
-       /* The Telnet panel. Accelerators used: [acgoh] svldr bftk */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       if (dlgtype == 0) {
-           bartitle(&cp, "Options controlling Telnet connections",
-                    IDC_TITLE_TELNET);
-           beginbox(&cp, "Data to send to the server", IDC_BOX_TELNET1);
-           staticedit(&cp, "Terminal-&speed string", IDC_TSSTATIC,
-                      IDC_TSEDIT, 50);
-           envsetter(&cp, "Environment variables:", IDC_ENVSTATIC,
-                     "&Variable", IDC_VARSTATIC, IDC_VAREDIT, "Va&lue",
-                     IDC_VALSTATIC, IDC_VALEDIT, IDC_ENVLIST, "A&dd",
-                     IDC_ENVADD, "&Remove", IDC_ENVREMOVE);
-           endbox(&cp);
-           beginbox(&cp, "Telnet protocol adjustments", IDC_BOX_TELNET2);
-           radioline(&cp, "Handling of OLD_ENVIRON ambiguity:",
-                     IDC_EMSTATIC, 2, "&BSD (commonplace)", IDC_EMBSD,
-                     "R&FC 1408 (unusual)", IDC_EMRFC, NULL);
-           radioline(&cp, "&Telnet negotiation mode:", IDC_ACTSTATIC, 2,
-                     "Passive", IDC_TPASSIVE, "Active",
-                     IDC_TACTIVE, NULL);
-           checkbox(&cp, "&Keyboard sends telnet Backspace and Interrupt",
-                    IDC_TELNETKEY);
-           checkbox(&cp, "Return key sends telnet New Line instead of ^M",
-                    IDC_TELNETRET);
-           endbox(&cp);
-       }
-    }
-
-    if (panel == rloginpanelstart) {
-       /* The Rlogin panel. Accelerators used: [acgoh] sl */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       if (dlgtype == 0) {
-           bartitle(&cp, "Options controlling Rlogin connections",
-                    IDC_TITLE_RLOGIN);
-           beginbox(&cp, "Data to send to the server", IDC_BOX_RLOGIN1);
-           staticedit(&cp, "Terminal-&speed string", IDC_R_TSSTATIC,
-                      IDC_R_TSEDIT, 50);
-           staticedit(&cp, "&Local username:", IDC_RLLUSERSTATIC,
-                      IDC_RLLUSEREDIT, 50);
-           endbox(&cp);
-       }
-    }
-
-    if (panel == sshpanelstart) {
-       /* The SSH panel. Accelerators used: [acgoh] r pel12n sud i */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       if (dlgtype == 0) {
-           bartitle(&cp, "Options controlling SSH connections",
-                    IDC_TITLE_SSH);
-           beginbox(&cp, "Data to send to the server", IDC_BOX_SSH1);
-           multiedit(&cp,
-                     "&Remote command:", IDC_CMDSTATIC, IDC_CMDEDIT, 100,
-                     NULL);
-           endbox(&cp);
-           beginbox(&cp, "Protocol options", IDC_BOX_SSH2);
-           checkbox(&cp, "Don't allocate a &pseudo-terminal", IDC_NOPTY);
-           checkbox(&cp, "Enable compr&ession", IDC_COMPRESS);
-           radioline(&cp, "Preferred SSH protocol version:",
-                     IDC_SSHPROTSTATIC, 4,
-                     "1 on&ly", IDC_SSHPROT1ONLY,
-                     "&1", IDC_SSHPROT1, "&2", IDC_SSHPROT2,
-                     "2 o&nly", IDC_SSHPROT2ONLY, NULL);
-           endbox(&cp);
-           beginbox(&cp, "Encryption options", IDC_BOX_SSH3);
-           /* Adds accelerators: ud */
-           prefslist(&cipherlist, &cp, "Encryption cipher &selection policy:",
-                     IDC_CIPHERSTATIC2, IDC_CIPHERLIST, IDC_CIPHERUP,
-                     IDC_CIPHERDN);
-           checkbox(&cp, "Enable non-standard use of s&ingle-DES in SSH 2",
-                    IDC_SSH2DES);
-           endbox(&cp);
-       }
-    }
-
-    if (panel == sshauthpanelstart) {
-       /* The SSH authentication panel. Accelerators used: [acgoh] m fkiuw */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       if (dlgtype == 0) {
-           bartitle(&cp, "Options controlling SSH authentication",
-                    IDC_TITLE_SSHAUTH);
-           beginbox(&cp, "Authentication methods",
-                    IDC_BOX_SSHAUTH1);
-           checkbox(&cp, "Atte&mpt TIS or CryptoCard authentication (SSH1)",
-                    IDC_AUTHTIS);
-           checkbox(&cp, "Attempt \"keyboard-&interactive\" authentication"
-                    " (SSH2)", IDC_AUTHKI);
-           endbox(&cp);
-           beginbox(&cp, "Authentication parameters",
-                    IDC_BOX_SSHAUTH2);
-           checkbox(&cp, "Allow agent &forwarding", IDC_AGENTFWD);
-           checkbox(&cp, "Allow attempted changes of &username in SSH2",
-                    IDC_CHANGEUSER);
-           editbutton(&cp, "Private &key file for authentication:",
-                      IDC_PKSTATIC, IDC_PKEDIT, "Bro&wse...",
-                      IDC_PKBUTTON);
-           endbox(&cp);
-       }
-    }
-
-    if (panel == sshbugspanelstart) {
-       /* The SSH bugs panel. Accelerators used: [acgoh] isrmepd */
-       struct ctlpos cp;
+    if (!path[0]) {
+       /*
+        * Here we must create the basic standard controls.
+        */
+       ctlposinit(&cp, hwnd, 3, 3, 235);
+       wc = &ctrls_base;
+       base_id = IDCX_STDBASE;
+    } else {
+       /*
+        * Otherwise, we're creating the controls for a particular
+        * panel.
+        */
        ctlposinit(&cp, hwnd, 80, 3, 13);
-       if (dlgtype == 0) {
-           bartitle(&cp, "Workarounds for SSH server bugs",
-                    IDC_TITLE_SSHBUGS);
-           beginbox(&cp, "Detection of known bugs in SSH servers",
-                    IDC_BOX_SSHBUGS1);
-           staticddl(&cp, "Chokes on SSH1 &ignore messages",
-                     IDC_BUGS_IGNORE1, IDC_BUGD_IGNORE1, 20);
-           staticddl(&cp, "Refuses all SSH1 pa&ssword camouflage",
-                     IDC_BUGS_PLAINPW1, IDC_BUGD_PLAINPW1, 20);
-           staticddl(&cp, "Chokes on SSH1 &RSA authentication",
-                     IDC_BUGS_RSA1, IDC_BUGD_RSA1, 20);
-           staticddl(&cp, "Miscomputes SSH2 H&MAC keys",
-                     IDC_BUGS_HMAC2, IDC_BUGD_HMAC2, 20);
-           staticddl(&cp, "Miscomputes SSH2 &encryption keys",
-                     IDC_BUGS_DERIVEKEY2, IDC_BUGD_DERIVEKEY2, 20);
-           staticddl(&cp, "Requires &padding on SSH2 RSA signatures",
-                     IDC_BUGS_RSAPAD2, IDC_BUGD_RSAPAD2, 20);
-           staticddl(&cp, "Chokes on &Diffie-Hellman group exchange",
-                     IDC_BUGS_DHGEX2, IDC_BUGD_DHGEX2, 20);
-           staticddl(&cp, "Misuses the sessio&n ID in PK auth",
-                     IDC_BUGS_PKSESSID2, IDC_BUGD_PKSESSID2, 20);
-           endbox(&cp);
-       }
+       wc = &ctrls_panel;
+       base_id = IDCX_PANELBASE;
     }
 
-    if (panel == tunnelspanelstart) {
-       /* The Tunnels panel. Accelerators used: [acgoh] exu tprsdilm */
-       struct ctlpos cp;
-       ctlposinit(&cp, hwnd, 80, 3, 13);
-       if (dlgtype == 0) {
-           bartitle(&cp, "Options controlling SSH tunnelling",
-                    IDC_TITLE_TUNNELS);
-           beginbox(&cp, "X11 forwarding", IDC_BOX_TUNNELS1);
-           checkbox(&cp, "&Enable X11 forwarding", IDC_X11_FORWARD);
-           staticedit(&cp, "&X display location", IDC_X11_DISPSTATIC,
-                     IDC_X11_DISPLAY, 50);
-           radioline(&cp, "Remote X11 a&uthentication protocol",
-                     IDC_X11AUTHSTATIC, 2,
-                     "MIT-Magic-Cookie-1", IDC_X11MIT,
-                     "XDM-Authorization-1", IDC_X11XDM, NULL);
-           endbox(&cp);
-           beginbox(&cp, "Port forwarding", IDC_BOX_TUNNELS2);
-           checkbox(&cp, "Local ports accept connections from o&ther hosts",
-                    IDC_LPORT_ALL);
-           checkbox(&cp, "Remote &ports do the same (SSH v2 only)",
-                    IDC_RPORT_ALL);
-           staticbtn(&cp, "Forwarded ports:", IDC_PFWDSTATIC,
-                     "&Remove", IDC_PFWDREMOVE);
-           fwdsetter(&cp, IDC_PFWDLIST,
-                     "Add new forwarded port:", IDC_PFWDSTATIC2,
-                     "&Source port", IDC_SPORTSTATIC, IDC_SPORTEDIT,
-                     "Dest&ination", IDC_DPORTSTATIC, IDC_DPORTEDIT,
-                     "A&dd", IDC_PFWDADD,
-                     "&Local", IDC_PFWDLOCAL,
-                     "Re&mote", IDC_PFWDREMOTE);
-           endbox(&cp);
-
-       }
+    for (index=-1; (index = ctrl_find_path(ctrlbox, path, index)) >= 0 ;) {
+       struct controlset *s = ctrlbox->ctrlsets[index];
+       winctrl_layout(&dp, wc, &cp, s, &base_id);
     }
 }
 
-/* 
- * Helper function to load the session selected in SESSLIST
- * if any, as this is done in more than one place in
- * GenericMainDlgProc(). 0 => failure.
- */
-static int load_selected_session(HWND hwnd)
-{
-    int n = SendDlgItemMessage(hwnd, IDC_SESSLIST,
-                              LB_GETCURSEL, 0, 0);
-    int isdef;
-    if (n == LB_ERR) {
-       MessageBeep(0);
-       return 0;
-    }
-    isdef = !strcmp(sesslist.sessions[n], "Default Settings");
-    load_settings(sesslist.sessions[n], !isdef, &cfg);
-    init_dlg_ctrls(hwnd, TRUE);
-    if (!isdef)
-       SetDlgItemText(hwnd, IDC_SESSEDIT, sesslist.sessions[n]);
-    else
-       SetDlgItemText(hwnd, IDC_SESSEDIT, "");
-    /* Restore the selection, which will have been clobbered by
-     * SESSEDIT handling. */
-    SendDlgItemMessage(hwnd, IDC_SESSLIST, LB_SETCURSEL, n, 0);
-    return 1;
-}
-
 /*
  * This function is the configuration box.
  */
-static int GenericMainDlgProc(HWND hwnd, UINT msg,
-                             WPARAM wParam, LPARAM lParam, int dlgtype)
+static int CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg,
+                                      WPARAM wParam, LPARAM lParam)
 {
     HWND hw, treeview;
     struct treeview_faff tvfaff;
-    HTREEITEM hsession;
-    OPENFILENAME of;
-    char filename[sizeof(cfg.keyfile.path)];
-    CHOOSEFONT cf;
-    LOGFONT lf;
-    char fontstatic[256];
-    char portname[32];
-    struct servent *service;
-    int i;
-    static UINT draglistmsg = WM_NULL;
+    int ret;
 
     switch (msg) {
       case WM_INITDIALOG:
-       readytogo = 0;
+       dp.hwnd = hwnd;
+       create_controls(hwnd, "");     /* Open and Cancel buttons etc */
        SetWindowLong(hwnd, GWL_USERDATA, 0);
         if (help_path)
             SetWindowLong(hwnd, GWL_EXSTYLE,
@@ -2213,52 +379,73 @@ static int GenericMainDlgProc(HWND hwnd, UINT msg,
        /*
         * Set up the tree view contents.
         */
-       hsession = treeview_insert(&tvfaff, 0, "Session");
-       treeview_insert(&tvfaff, 1, "Logging");
-       treeview_insert(&tvfaff, 0, "Terminal");
-       treeview_insert(&tvfaff, 1, "Keyboard");
-       treeview_insert(&tvfaff, 1, "Bell");
-       treeview_insert(&tvfaff, 1, "Features");
-       treeview_insert(&tvfaff, 0, "Window");
-       treeview_insert(&tvfaff, 1, "Appearance");
-       treeview_insert(&tvfaff, 1, "Behaviour");
-       treeview_insert(&tvfaff, 1, "Translation");
-       treeview_insert(&tvfaff, 1, "Selection");
-       treeview_insert(&tvfaff, 1, "Colours");
-       treeview_insert(&tvfaff, 0, "Connection");
-       if (dlgtype == 0) {
-           treeview_insert(&tvfaff, 1, "Proxy");
-           treeview_insert(&tvfaff, 1, "Telnet");
-           treeview_insert(&tvfaff, 1, "Rlogin");
-           if (backends[3].backend != NULL) {
-               treeview_insert(&tvfaff, 1, "SSH");
-               /* XXX long name is ugly */
-               /* XXX make it closed by default? */
-               treeview_insert(&tvfaff, 2, "Auth");
-               treeview_insert(&tvfaff, 2, "Tunnels");
-               treeview_insert(&tvfaff, 2, "Bugs");
+       {
+           HTREEITEM hfirst = NULL;
+           int i;
+           char *path = NULL;
+
+           for (i = 0; i < ctrlbox->nctrlsets; i++) {
+               struct controlset *s = ctrlbox->ctrlsets[i];
+               HTREEITEM item;
+               int j;
+               char *c;
+
+               if (!s->pathname[0])
+                   continue;
+               j = path ? ctrl_path_compare(s->pathname, path) : 0;
+               if (j == INT_MAX)
+                   continue;          /* same path, nothing to add to tree */
+
+               /*
+                * We expect never to find an implicit path
+                * component. For example, we expect never to see
+                * A/B/C followed by A/D/E, because that would
+                * _implicitly_ create A/D. All our path prefixes
+                * are expected to contain actual controls and be
+                * selectable in the treeview; so we would expect
+                * to see A/D _explicitly_ before encountering
+                * A/D/E.
+                */
+               assert(j == ctrl_path_elements(s->pathname) - 1);
+
+               c = strrchr(s->pathname, '/');
+               if (!c)
+                       c = s->pathname;
+               else
+                       c++;
+
+               item = treeview_insert(&tvfaff, j, c, s->pathname);
+               if (!hfirst)
+                   hfirst = item;
+
+               path = s->pathname;
            }
-       }
 
-       /*
-        * Put the treeview selection on to the Session panel. This
-        * should also cause creation of the relevant controls.
-        */
-       TreeView_SelectItem(treeview, hsession);
+           /*
+            * Put the treeview selection on to the Session panel.
+            * This should also cause creation of the relevant
+            * controls.
+            */
+           TreeView_SelectItem(treeview, hfirst);
+       }
 
        /*
         * Set focus into the first available control.
         */
        {
-           HWND ctl;
-           ctl = GetDlgItem(hwnd, IDC_HOST);
-           if (!ctl)
-               ctl = GetDlgItem(hwnd, IDC_CLOSEEXIT);
-           SetFocus(ctl);
+           int i;
+           struct winctrl *c;
+
+           for (i = 0; (c = winctrl_findbyindex(&ctrls_panel, i)) != NULL;
+                i++) {
+               if (c->ctrl) {
+                   dlg_set_focus(c->ctrl, &dp);
+                   break;
+               }
+           }
        }
 
        SetWindowLong(hwnd, GWL_USERDATA, 1);
-       sesslist_has_focus = 0;
        return 0;
       case WM_LBUTTONUP:
        /*
@@ -2266,8 +453,8 @@ static int GenericMainDlgProc(HWND hwnd, UINT msg,
         * previous double click on the session list.
         */
        ReleaseCapture();
-       if (readytogo)
-           SendMessage(hwnd, WM_COMMAND, IDOK, 0);
+       if (dp.ended)
+           EndDialog(hwnd, dp.endresult ? 1 : 0);
        break;
       case WM_NOTIFY:
        if (LOWORD(wParam) == IDCX_TREEVIEW &&
@@ -2275,7 +462,6 @@ static int GenericMainDlgProc(HWND hwnd, UINT msg,
            HTREEITEM i =
                TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom);
            TVITEM item;
-           int j;
            char buffer[64];
  
            SendMessage (hwnd, WM_SETREDRAW, FALSE, 0);
@@ -2283,55 +469,29 @@ static int GenericMainDlgProc(HWND hwnd, UINT msg,
            item.hItem = i;
            item.pszText = buffer;
            item.cchTextMax = sizeof(buffer);
-           item.mask = TVIF_TEXT;
+           item.mask = TVIF_TEXT | TVIF_PARAM;
            TreeView_GetItem(((LPNMHDR) lParam)->hwndFrom, &item);
-           for (j = controlstartvalue; j < controlendvalue; j++) {
-               HWND item = GetDlgItem(hwnd, j);
-               if (item)
-                   DestroyWindow(item);
+           {
+               /* Destroy all controls in the currently visible panel. */
+               int k;
+               HWND item;
+               struct winctrl *c;
+
+               while ((c = winctrl_findbyindex(&ctrls_panel, 0)) != NULL) {
+                   for (k = 0; k < c->num_ids; k++) {
+                       item = GetDlgItem(hwnd, c->base_id + k);
+                       if (item)
+                           DestroyWindow(item);
+                   }
+                   winctrl_rem_shortcuts(&dp, c);
+                   winctrl_remove(&ctrls_panel, c);
+                   sfree(c->data);
+                   sfree(c);
+               }
            }
-           if (!strcmp(buffer, "Session"))
-               create_controls(hwnd, dlgtype, sessionpanelstart);
-           if (!strcmp(buffer, "Logging"))
-               create_controls(hwnd, dlgtype, loggingpanelstart);
-           if (!strcmp(buffer, "Keyboard"))
-               create_controls(hwnd, dlgtype, keyboardpanelstart);
-           if (!strcmp(buffer, "Terminal"))
-               create_controls(hwnd, dlgtype, terminalpanelstart);
-           if (!strcmp(buffer, "Bell"))
-               create_controls(hwnd, dlgtype, bellpanelstart);
-           if (!strcmp(buffer, "Features"))
-               create_controls(hwnd, dlgtype, featurespanelstart);
-           if (!strcmp(buffer, "Window"))
-               create_controls(hwnd, dlgtype, windowpanelstart);
-           if (!strcmp(buffer, "Appearance"))
-               create_controls(hwnd, dlgtype, appearancepanelstart);
-           if (!strcmp(buffer, "Behaviour"))
-               create_controls(hwnd, dlgtype, behaviourpanelstart);
-           if (!strcmp(buffer, "Tunnels"))
-               create_controls(hwnd, dlgtype, tunnelspanelstart);
-           if (!strcmp(buffer, "Connection"))
-               create_controls(hwnd, dlgtype, connectionpanelstart);
-           if (!strcmp(buffer, "Proxy"))
-               create_controls(hwnd, dlgtype, proxypanelstart);
-           if (!strcmp(buffer, "Telnet"))
-               create_controls(hwnd, dlgtype, telnetpanelstart);
-           if (!strcmp(buffer, "Rlogin"))
-               create_controls(hwnd, dlgtype, rloginpanelstart);
-           if (!strcmp(buffer, "SSH"))
-               create_controls(hwnd, dlgtype, sshpanelstart);
-           if (!strcmp(buffer, "Auth"))
-               create_controls(hwnd, dlgtype, sshauthpanelstart);
-           if (!strcmp(buffer, "Bugs"))
-               create_controls(hwnd, dlgtype, sshbugspanelstart);
-           if (!strcmp(buffer, "Selection"))
-               create_controls(hwnd, dlgtype, selectionpanelstart);
-           if (!strcmp(buffer, "Colours"))
-               create_controls(hwnd, dlgtype, colourspanelstart);
-           if (!strcmp(buffer, "Translation"))
-               create_controls(hwnd, dlgtype, translationpanelstart);
-
-           init_dlg_ctrls(hwnd, FALSE);
+           create_controls(hwnd, (char *)item.lParam);
+
+           dlg_refresh(NULL, &dp);    /* set up control values */
  
            SendMessage (hwnd, WM_SETREDRAW, TRUE, 0);
            InvalidateRect (hwnd, NULL, TRUE);
@@ -2341,1377 +501,25 @@ static int GenericMainDlgProc(HWND hwnd, UINT msg,
        }
        break;
       case WM_COMMAND:
+      case WM_DRAWITEM:
+      default:                        /* also handle drag list msg here */
        /*
         * Only process WM_COMMAND once the dialog is fully formed.
         */
-       if (GetWindowLong(hwnd, GWL_USERDATA) == 1)
-           switch (LOWORD(wParam)) {
-             case IDOK:
-               /* Behaviour of the "Open" button is different if the
-                * session list has focus, *unless* the user just
-                * double-clicked... */
-               if (sesslist_has_focus && !readytogo) {
-                   if (!load_selected_session(hwnd)) {
-                       MessageBeep(0);
-                       return 0;
-                   }
-               }
-               /* If at this point we have a valid session, go! */
-               if (*cfg.host) {
-                    if (requested_help) {
-                        WinHelp(hwnd, help_path, HELP_QUIT, 0);
-                        requested_help = FALSE;
-                    }
-                   EndDialog(hwnd, 1);
-                } else
-                   MessageBeep(0);
-               return 0;
-             case IDC_HELPBTN:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                    if (help_path) {
-                        WinHelp(hwnd, help_path,
-                                help_has_contents ? HELP_FINDER : HELP_CONTENTS,
-                                0);
-                        requested_help = TRUE;
-                    }
-                }
-                break;
-             case IDCANCEL:
-                if (requested_help) {
-                    WinHelp(hwnd, help_path, HELP_QUIT, 0);
-                    requested_help = FALSE;
-                }
-               EndDialog(hwnd, 0);
-               return 0;
-             case IDC_PROTTELNET:
-             case IDC_PROTRLOGIN:
-             case IDC_PROTSSH:
-             case IDC_PROTRAW:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   int i = IsDlgButtonChecked(hwnd, IDC_PROTSSH);
-                   int j = IsDlgButtonChecked(hwnd, IDC_PROTTELNET);
-                   int k = IsDlgButtonChecked(hwnd, IDC_PROTRLOGIN);
-                   cfg.protocol =
-                       i ? PROT_SSH : j ? PROT_TELNET : k ? PROT_RLOGIN :
-                       PROT_RAW;
-                   /*
-                    * When switching using the arrow keys, we
-                    * appear to get two of these messages, both
-                    * mentioning the target button in
-                    * LOWORD(wParam), but one of them called while
-                    * the previous button is still checked. This
-                    * causes an unnecessary reset of the port
-                    * number field, which we fix by ensuring here
-                    * that the button selected is indeed the one
-                    * checked.
-                    */
-                   if (IsDlgButtonChecked(hwnd, LOWORD(wParam)) &&
-                       ((cfg.protocol == PROT_SSH && cfg.port != 22)
-                        || (cfg.protocol == PROT_TELNET && cfg.port != 23)
-                        || (cfg.protocol == PROT_RLOGIN
-                            && cfg.port != 513))) {
-                       cfg.port = i ? 22 : j ? 23 : 513;
-                       SetDlgItemInt(hwnd, IDC_PORT, cfg.port, FALSE);
-                   }
-               }
-               break;
-             case IDC_HOST:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   GetDlgItemText(hwnd, IDC_HOST, cfg.host,
-                                  sizeof(cfg.host) - 1);
-               break;
-             case IDC_PORT:
-               if (HIWORD(wParam) == EN_CHANGE) {
-                   GetDlgItemText(hwnd, IDC_PORT, portname, 31);
-                   if (isdigit(portname[0]))
-                       MyGetDlgItemInt(hwnd, IDC_PORT, &cfg.port);
-                   else {
-                       service = getservbyname(portname, NULL);
-                       if (service)
-                           cfg.port = ntohs(service->s_port);
-                       else
-                           cfg.port = 0;
-                   }
-               }
-               break;
-             case IDC_SESSEDIT:
-               if (HIWORD(wParam) == EN_CHANGE) {
-                   SendDlgItemMessage(hwnd, IDC_SESSLIST, LB_SETCURSEL,
-                                      (WPARAM) - 1, 0);
-                   GetDlgItemText(hwnd, IDC_SESSEDIT,
-                                  savedsession, sizeof(savedsession) - 1);
-                   savedsession[sizeof(savedsession) - 1] = '\0';
-               }
-               break;
-             case IDC_SESSSAVE:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   /*
-                    * Save a session
-                    */
-                   char str[2048];
-                   GetDlgItemText(hwnd, IDC_SESSEDIT, str,
-                                  sizeof(str) - 1);
-                   if (!*str) {
-                       int n = SendDlgItemMessage(hwnd, IDC_SESSLIST,
-                                                  LB_GETCURSEL, 0, 0);
-                       if (n == LB_ERR) {
-                           MessageBeep(0);
-                           break;
-                       }
-                       strcpy(str, sesslist.sessions[n]);
-                   }
-                   save_settings(str, !!strcmp(str, "Default Settings"),
-                                 &cfg);
-                   get_sesslist(&sesslist, FALSE);
-                   get_sesslist(&sesslist, TRUE);
-                   SendDlgItemMessage(hwnd, IDC_SESSLIST, WM_SETREDRAW,
-                                      FALSE, 0);
-                   SendDlgItemMessage(hwnd, IDC_SESSLIST, LB_RESETCONTENT,
-                                      0, 0);
-                   for (i = 0; i < sesslist.nsessions; i++)
-                       SendDlgItemMessage(hwnd, IDC_SESSLIST,
-                                          LB_ADDSTRING, 0,
-                                          (LPARAM) (sesslist.sessions[i]));
-                   SendDlgItemMessage(hwnd, IDC_SESSLIST, LB_SETCURSEL,
-                                      (WPARAM) - 1, 0);
-                   SendDlgItemMessage(hwnd, IDC_SESSLIST, WM_SETREDRAW,
-                                      TRUE, 0);
-                   InvalidateRect(GetDlgItem(hwnd, IDC_SESSLIST), NULL,
-                                  TRUE);
-               }
-               break;
-             case IDC_SESSLIST:
-             case IDC_SESSLOAD:
-               if (LOWORD(wParam) == IDC_SESSLIST) {
-                   if (HIWORD(wParam) == LBN_SETFOCUS)
-                       sesslist_has_focus = 1;
-                   else if (HIWORD(wParam) == LBN_KILLFOCUS)
-                       sesslist_has_focus = 0;
-               }
-               if (LOWORD(wParam) == IDC_SESSLOAD &&
-                   HIWORD(wParam) != BN_CLICKED &&
-                   HIWORD(wParam) != BN_DOUBLECLICKED) break;
-               if (LOWORD(wParam) == IDC_SESSLIST &&
-                   HIWORD(wParam) != LBN_DBLCLK) break;
-               /* Load the session selected in SESSLIST. */
-               if (load_selected_session(hwnd) &&
-                   LOWORD(wParam) == IDC_SESSLIST) {
-                   /*
-                    * A double-click on a saved session should
-                    * actually start the session, not just load it.
-                    * Unless it's Default Settings or some other
-                    * host-less set of saved settings.
-                    */
-                   if (*cfg.host) {
-                       readytogo = TRUE;
-                       SetCapture(hwnd);
-                   }
-               }
-               break;
-             case IDC_SESSDEL:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   int n = SendDlgItemMessage(hwnd, IDC_SESSLIST,
-                                              LB_GETCURSEL, 0, 0);
-                   if (n == LB_ERR || n == 0) {
-                       MessageBeep(0);
-                       break;
-                   }
-                   del_settings(sesslist.sessions[n]);
-                   get_sesslist(&sesslist, FALSE);
-                   get_sesslist(&sesslist, TRUE);
-                   SendDlgItemMessage(hwnd, IDC_SESSLIST, WM_SETREDRAW,
-                                      FALSE, 0);
-                   SendDlgItemMessage(hwnd, IDC_SESSLIST, LB_RESETCONTENT,
-                                      0, 0);
-                   for (i = 0; i < sesslist.nsessions; i++)
-                       SendDlgItemMessage(hwnd, IDC_SESSLIST,
-                                          LB_ADDSTRING, 0,
-                                          (LPARAM) (sesslist.sessions[i]));
-                   SendDlgItemMessage(hwnd, IDC_SESSLIST, LB_SETCURSEL,
-                                      (WPARAM) - 1, 0);
-                   SendDlgItemMessage(hwnd, IDC_SESSLIST, WM_SETREDRAW,
-                                      TRUE, 0);
-                   InvalidateRect(GetDlgItem(hwnd, IDC_SESSLIST), NULL,
-                                  TRUE);
-               }
-             case IDC_PINGEDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   MyGetDlgItemInt(hwnd, IDC_PINGEDIT,
-                                   &cfg.ping_interval);
-               break;
-             case IDC_NODELAY:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.tcp_nodelay =
-                       IsDlgButtonChecked(hwnd, IDC_NODELAY);
-               break;
-             case IDC_DEL008:
-             case IDC_DEL127:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.bksp_is_delete =
-                       IsDlgButtonChecked(hwnd, IDC_DEL127);
-               break;
-             case IDC_HOMETILDE:
-             case IDC_HOMERXVT:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.rxvt_homeend =
-                       IsDlgButtonChecked(hwnd, IDC_HOMERXVT);
-               break;
-             case IDC_FUNCTILDE:
-             case IDC_FUNCLINUX:
-             case IDC_FUNCXTERM:
-             case IDC_FUNCVT400:
-             case IDC_FUNCVT100P:
-             case IDC_FUNCSCO:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       switch (LOWORD(wParam)) {
-                     case IDC_FUNCTILDE:
-                       cfg.funky_type = 0;
-                       break;
-                     case IDC_FUNCLINUX:
-                       cfg.funky_type = 1;
-                       break;
-                     case IDC_FUNCXTERM:
-                       cfg.funky_type = 2;
-                       break;
-                     case IDC_FUNCVT400:
-                       cfg.funky_type = 3;
-                       break;
-                     case IDC_FUNCVT100P:
-                       cfg.funky_type = 4;
-                       break;
-                     case IDC_FUNCSCO:
-                       cfg.funky_type = 5;
-                       break;
-                   }
-               break;
-             case IDC_KPNORMAL:
-             case IDC_KPAPPLIC:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   cfg.app_keypad =
-                       IsDlgButtonChecked(hwnd, IDC_KPAPPLIC);
-                   cfg.nethack_keypad = FALSE;
-               }
-               break;
-             case IDC_KPNH:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   cfg.app_keypad = FALSE;
-                   cfg.nethack_keypad = TRUE;
-               }
-               break;
-             case IDC_CURNORMAL:
-             case IDC_CURAPPLIC:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.app_cursor =
-                       IsDlgButtonChecked(hwnd, IDC_CURAPPLIC);
-               break;
-             case IDC_NOAPPLICC:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.no_applic_c =
-                       IsDlgButtonChecked(hwnd, IDC_NOAPPLICC);
-               break;
-             case IDC_NOAPPLICK:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.no_applic_k =
-                       IsDlgButtonChecked(hwnd, IDC_NOAPPLICK);
-               break;
-             case IDC_NOMOUSEREP:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.no_mouse_rep =
-                       IsDlgButtonChecked(hwnd, IDC_NOMOUSEREP);
-               break;
-             case IDC_NORESIZE:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.no_remote_resize =
-                       IsDlgButtonChecked(hwnd, IDC_NORESIZE);
-               break;
-             case IDC_NOALTSCREEN:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.no_alt_screen =
-                       IsDlgButtonChecked(hwnd, IDC_NOALTSCREEN);
-               break;
-             case IDC_NOWINTITLE:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.no_remote_wintitle =
-                       IsDlgButtonChecked(hwnd, IDC_NOWINTITLE);
-               break;
-             case IDC_NODBACKSPACE:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.no_dbackspace =
-                       IsDlgButtonChecked(hwnd, IDC_NODBACKSPACE);
-               break;
-             case IDC_NOCHARSET:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.no_remote_charset =
-                       IsDlgButtonChecked(hwnd, IDC_NOCHARSET);
-               break;
-             case IDC_ALTF4:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.alt_f4 = IsDlgButtonChecked(hwnd, IDC_ALTF4);
-               break;
-             case IDC_ALTSPACE:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.alt_space =
-                       IsDlgButtonChecked(hwnd, IDC_ALTSPACE);
-               break;
-             case IDC_ALTONLY:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.alt_only =
-                       IsDlgButtonChecked(hwnd, IDC_ALTONLY);
-               break;
-             case IDC_ECHOBACKEND:
-             case IDC_ECHOYES:
-             case IDC_ECHONO:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   if (LOWORD(wParam) == IDC_ECHOBACKEND)
-                       cfg.localecho = AUTO;
-                   if (LOWORD(wParam) == IDC_ECHOYES)
-                       cfg.localecho = FORCE_ON;
-                   if (LOWORD(wParam) == IDC_ECHONO)
-                       cfg.localecho = FORCE_OFF;
-               }
-               break;
-             case IDC_EDITBACKEND:
-             case IDC_EDITYES:
-             case IDC_EDITNO:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   if (LOWORD(wParam) == IDC_EDITBACKEND)
-                       cfg.localedit = AUTO;
-                   if (LOWORD(wParam) == IDC_EDITYES)
-                       cfg.localedit = FORCE_ON;
-                   if (LOWORD(wParam) == IDC_EDITNO)
-                       cfg.localedit = FORCE_OFF;
-               }
-               break;
-             case IDC_ANSWEREDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   GetDlgItemText(hwnd, IDC_ANSWEREDIT, cfg.answerback,
-                                  sizeof(cfg.answerback) - 1);
-               break;
-             case IDC_ALWAYSONTOP:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.alwaysontop =
-                       IsDlgButtonChecked(hwnd, IDC_ALWAYSONTOP);
-               break;
-             case IDC_FULLSCREENONALTENTER:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.fullscreenonaltenter =
-                       IsDlgButtonChecked(hwnd, IDC_FULLSCREENONALTENTER);
-               break;
-             case IDC_SCROLLKEY:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.scroll_on_key =
-                       IsDlgButtonChecked(hwnd, IDC_SCROLLKEY);
-               break;
-             case IDC_SCROLLDISP:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.scroll_on_disp =
-                       IsDlgButtonChecked(hwnd, IDC_SCROLLDISP);
-               break;
-             case IDC_COMPOSEKEY:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.compose_key =
-                       IsDlgButtonChecked(hwnd, IDC_COMPOSEKEY);
-               break;
-             case IDC_CTRLALTKEYS:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.ctrlaltkeys =
-                       IsDlgButtonChecked(hwnd, IDC_CTRLALTKEYS);
-               break;
-             case IDC_TELNETKEY:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.telnet_keyboard =
-                       IsDlgButtonChecked(hwnd, IDC_TELNETKEY);
-               break;
-             case IDC_TELNETRET:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.telnet_newline =
-                       IsDlgButtonChecked(hwnd, IDC_TELNETRET);
-               break;
-             case IDC_WRAPMODE:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.wrap_mode =
-                       IsDlgButtonChecked(hwnd, IDC_WRAPMODE);
-               break;
-             case IDC_DECOM:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.dec_om = IsDlgButtonChecked(hwnd, IDC_DECOM);
-               break;
-             case IDC_LFHASCR:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.lfhascr =
-                       IsDlgButtonChecked(hwnd, IDC_LFHASCR);
-               break;
-             case IDC_ROWSEDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   MyGetDlgItemInt(hwnd, IDC_ROWSEDIT, &cfg.height);
-               break;
-             case IDC_COLSEDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   MyGetDlgItemInt(hwnd, IDC_COLSEDIT, &cfg.width);
-               break;
-             case IDC_SAVEEDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   MyGetDlgItemInt(hwnd, IDC_SAVEEDIT, &cfg.savelines);
-               break;
-             case IDC_CHOOSEFONT:
-               {
-                   HDC hdc = GetDC(0);
-                   lf.lfHeight = -MulDiv(cfg.font.height,
-                                         GetDeviceCaps(hdc, LOGPIXELSY),
-                                         72);
-                   ReleaseDC(0, hdc);
-               }
-               lf.lfWidth = lf.lfEscapement = lf.lfOrientation = 0;
-               lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = 0;
-               lf.lfWeight = (cfg.font.isbold ? FW_BOLD : 0);
-               lf.lfCharSet = cfg.font.charset;
-               lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
-               lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
-               lf.lfQuality = DEFAULT_QUALITY;
-               lf.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
-               strncpy(lf.lfFaceName, cfg.font.name,
-                       sizeof(lf.lfFaceName) - 1);
-               lf.lfFaceName[sizeof(lf.lfFaceName) - 1] = '\0';
-
-               cf.lStructSize = sizeof(cf);
-               cf.hwndOwner = hwnd;
-               cf.lpLogFont = &lf;
-               cf.Flags = CF_FIXEDPITCHONLY | CF_FORCEFONTEXIST |
-                   CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
-
-               if (ChooseFont(&cf)) {
-                   strncpy(cfg.font.name, lf.lfFaceName,
-                           sizeof(cfg.font.name) - 1);
-                   cfg.font.name[sizeof(cfg.font.name) - 1] = '\0';
-                   cfg.font.isbold = (lf.lfWeight == FW_BOLD);
-                   cfg.font.charset = lf.lfCharSet;
-                   cfg.font.height = cf.iPointSize / 10;
-                   fmtfont(fontstatic);
-                   SetDlgItemText(hwnd, IDC_FONTSTATIC, fontstatic);
-               }
-               break;
-             case IDC_BELL_DISABLED:
-             case IDC_BELL_DEFAULT:
-             case IDC_BELL_WAVEFILE:
-             case IDC_BELL_VISUAL:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   if (LOWORD(wParam) == IDC_BELL_DISABLED)
-                       cfg.beep = BELL_DISABLED;
-                   if (LOWORD(wParam) == IDC_BELL_DEFAULT)
-                       cfg.beep = BELL_DEFAULT;
-                   if (LOWORD(wParam) == IDC_BELL_WAVEFILE)
-                       cfg.beep = BELL_WAVEFILE;
-                   if (LOWORD(wParam) == IDC_BELL_VISUAL)
-                       cfg.beep = BELL_VISUAL;
-               }
-               break;
-             case IDC_B_IND_DISABLED:
-             case IDC_B_IND_FLASH:
-             case IDC_B_IND_STEADY:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   if (LOWORD(wParam) == IDC_B_IND_DISABLED)
-                       cfg.beep_ind = B_IND_DISABLED;
-                   if (LOWORD(wParam) == IDC_B_IND_FLASH)
-                       cfg.beep_ind = B_IND_FLASH;
-                   if (LOWORD(wParam) == IDC_B_IND_STEADY)
-                       cfg.beep_ind = B_IND_STEADY;
-               }
-               break;
-             case IDC_BELL_WAVEBROWSE:
-               memset(&of, 0, sizeof(of));
-#ifdef OPENFILENAME_SIZE_VERSION_400
-               of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
-#else
-               of.lStructSize = sizeof(of);
-#endif
-               of.hwndOwner = hwnd;
-               of.lpstrFilter = "Wave Files (*.wav)\0*.WAV\0"
-                   "All Files (*.*)\0*\0\0\0";
-               of.lpstrCustomFilter = NULL;
-               of.nFilterIndex = 1;
-               of.lpstrFile = filename;
-               strcpy(filename, cfg.bell_wavefile.path);
-               of.nMaxFile = sizeof(filename);
-               of.lpstrFileTitle = NULL;
-               of.lpstrInitialDir = NULL;
-               of.lpstrTitle = "Select Bell Sound File";
-               of.Flags = 0;
-               if (GetOpenFileName(&of)) {
-                   strcpy(cfg.bell_wavefile.path, filename);
-                   SetDlgItemText(hwnd, IDC_BELL_WAVEEDIT,
-                                  cfg.bell_wavefile.path);
-               }
-               break;
-             case IDC_BELL_WAVEEDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   GetDlgItemText(hwnd, IDC_BELL_WAVEEDIT,
-                                  cfg.bell_wavefile.path,
-                                  sizeof(cfg.bell_wavefile.path) - 1);
-               break;
-             case IDC_BELLOVL:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.bellovl =
-                       IsDlgButtonChecked(hwnd, IDC_BELLOVL);
-               break;
-             case IDC_BELLOVLN:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   MyGetDlgItemInt(hwnd, IDC_BELLOVLN, &cfg.bellovl_n);
-               break;
-             case IDC_BELLOVLT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   MyGetDlgItemFlt(hwnd, IDC_BELLOVLT, &cfg.bellovl_t,
-                                   1000);
-               break;
-             case IDC_BELLOVLS:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   MyGetDlgItemFlt(hwnd, IDC_BELLOVLS, &cfg.bellovl_s,
-                                   1000);
-               break;
-             case IDC_BLINKTEXT:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.blinktext =
-                       IsDlgButtonChecked(hwnd, IDC_BLINKTEXT);
-               break;
-             case IDC_BCE:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.bce = IsDlgButtonChecked(hwnd, IDC_BCE);
-               break;
-             case IDC_WINNAME:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.win_name_always =
-                       !IsDlgButtonChecked(hwnd, IDC_WINNAME);
-               break;
-             case IDC_HIDEMOUSE:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.hide_mouseptr =
-                       IsDlgButtonChecked(hwnd, IDC_HIDEMOUSE);
-               break;
-             case IDC_SUNKENEDGE:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.sunken_edge =
-                       IsDlgButtonChecked(hwnd, IDC_SUNKENEDGE);
-               break;
-             case IDC_WINBEDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   MyGetDlgItemInt(hwnd, IDC_WINBEDIT,
-                                   &cfg.window_border);
-               if (cfg.window_border > 32)
-                   cfg.window_border = 32;
-               break;
-             case IDC_CURBLOCK:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.cursor_type = 0;
-               break;
-             case IDC_CURUNDER:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.cursor_type = 1;
-               break;
-             case IDC_CURVERT:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.cursor_type = 2;
-               break;
-             case IDC_BLINKCUR:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.blink_cur =
-                       IsDlgButtonChecked(hwnd, IDC_BLINKCUR);
-               break;
-             case IDC_SCROLLBAR:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.scrollbar =
-                       IsDlgButtonChecked(hwnd, IDC_SCROLLBAR);
-               break;
-             case IDC_SCROLLBARFULLSCREEN:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                   cfg.scrollbar_in_fullscreen =
-                   IsDlgButtonChecked(hwnd, IDC_SCROLLBARFULLSCREEN);
-               break;
-             case IDC_RESIZETERM:
-             case IDC_RESIZEFONT:
-             case IDC_RESIZENONE:
-             case IDC_RESIZEEITHER:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   cfg.resize_action =
-                       IsDlgButtonChecked(hwnd,
-                                          IDC_RESIZETERM) ? RESIZE_TERM :
-                       IsDlgButtonChecked(hwnd,
-                                          IDC_RESIZEFONT) ? RESIZE_FONT :
-                       IsDlgButtonChecked(hwnd,
-                                          IDC_RESIZEEITHER) ? RESIZE_EITHER :
-                       RESIZE_DISABLED;
-               }
-               break;
-             case IDC_WINEDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   GetDlgItemText(hwnd, IDC_WINEDIT, cfg.wintitle,
-                                  sizeof(cfg.wintitle) - 1);
-               break;
-             case IDC_COEALWAYS:
-             case IDC_COENEVER:
-             case IDC_COENORMAL:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   cfg.close_on_exit =
-                       IsDlgButtonChecked(hwnd,
-                                          IDC_COEALWAYS) ? FORCE_ON :
-                       IsDlgButtonChecked(hwnd,
-                                          IDC_COENEVER) ? FORCE_OFF :
-                       AUTO;
-               }
-               break;
-             case IDC_CLOSEWARN:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.warn_on_close =
-                       IsDlgButtonChecked(hwnd, IDC_CLOSEWARN);
-               break;
-             case IDC_TTEDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   GetDlgItemText(hwnd, IDC_TTEDIT, cfg.termtype,
-                                  sizeof(cfg.termtype) - 1);
-               break;
-
-               /* proxy config */
-             case IDC_PROXYHOSTEDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   GetDlgItemText(hwnd, IDC_PROXYHOSTEDIT, cfg.proxy_host, 
-                                  sizeof(cfg.proxy_host) - 1);
-               break;
-             case IDC_PROXYPORTEDIT:
-               if (HIWORD(wParam) == EN_CHANGE) {
-                   GetDlgItemText(hwnd, IDC_PROXYPORTEDIT, portname, 31);
-                   if (isdigit(portname[0]))
-                       MyGetDlgItemInt(hwnd, IDC_PROXYPORTEDIT, &cfg.proxy_port);
-                   else {
-                       service = getservbyname(portname, NULL);
-                       if (service)
-                           cfg.proxy_port = ntohs(service->s_port);
-                       else
-                           cfg.proxy_port = 0;
-                   }
-               }
-               break;
-             case IDC_PROXYEXCLUDEEDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   GetDlgItemText(hwnd, IDC_PROXYEXCLUDEEDIT,
-                                  cfg.proxy_exclude_list,
-                                  sizeof(cfg.proxy_exclude_list) - 1);
-               break;
-             case IDC_PROXYUSEREDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   GetDlgItemText(hwnd, IDC_PROXYUSEREDIT,
-                                  cfg.proxy_username, 
-                                  sizeof(cfg.proxy_username) - 1);
-               break;
-             case IDC_PROXYPASSEDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   GetDlgItemText(hwnd, IDC_PROXYPASSEDIT,
-                                  cfg.proxy_password, 
-                                  sizeof(cfg.proxy_password) - 1);
-               break;
-             case IDC_PROXYTELNETCMDEDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   GetDlgItemText(hwnd, IDC_PROXYTELNETCMDEDIT,
-                                  cfg.proxy_telnet_command,
-                                  sizeof(cfg.proxy_telnet_command) - 1);
-               break;
-             case IDC_PROXYSOCKSVER5:
-             case IDC_PROXYSOCKSVER4:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   cfg.proxy_socks_version =
-                       IsDlgButtonChecked(hwnd, IDC_PROXYSOCKSVER4) ? 4 : 5;
-               }
-               break;
-             case IDC_PROXYLOCALHOST:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                   cfg.even_proxy_localhost =
-                   IsDlgButtonChecked(hwnd, IDC_PROXYLOCALHOST);
-               break;
-             case IDC_PROXYDNSNO:
-             case IDC_PROXYDNSAUTO:
-             case IDC_PROXYDNSYES:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   cfg.proxy_dns =
-                       IsDlgButtonChecked(hwnd, IDC_PROXYDNSNO) ? FORCE_OFF :
-                       IsDlgButtonChecked(hwnd, IDC_PROXYDNSYES) ? FORCE_ON :
-                       AUTO;
-               }
-               break;
-             case IDC_PROXYTYPENONE:
-             case IDC_PROXYTYPEHTTP:
-             case IDC_PROXYTYPESOCKS:
-             case IDC_PROXYTYPETELNET:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   cfg.proxy_type =
-                       IsDlgButtonChecked(hwnd, IDC_PROXYTYPEHTTP) ? PROXY_HTTP :
-                       IsDlgButtonChecked(hwnd, IDC_PROXYTYPESOCKS) ? PROXY_SOCKS :
-                       IsDlgButtonChecked(hwnd, IDC_PROXYTYPETELNET) ? PROXY_TELNET :
-                       PROXY_NONE;
-               }
-               break;
-
-             case IDC_LGFEDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   GetDlgItemText(hwnd, IDC_LGFEDIT, cfg.logfilename.path,
-                                  sizeof(cfg.logfilename.path) - 1);
-               break;
-             case IDC_LGFBUTTON:
-               memset(&of, 0, sizeof(of));
-#ifdef OPENFILENAME_SIZE_VERSION_400
-               of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
-#else
-               of.lStructSize = sizeof(of);
-#endif
-               of.hwndOwner = hwnd;
-               of.lpstrFilter = "All Files (*.*)\0*\0\0\0";
-               of.lpstrCustomFilter = NULL;
-               of.nFilterIndex = 1;
-               of.lpstrFile = filename;
-               strcpy(filename, cfg.logfilename.path);
-               of.nMaxFile = sizeof(filename);
-               of.lpstrFileTitle = NULL;
-               of.lpstrInitialDir = NULL;
-               of.lpstrTitle = "Select session log file";
-               of.Flags = 0;
-               if (GetSaveFileName(&of)) {
-                   strcpy(cfg.logfilename.path, filename);
-                   SetDlgItemText(hwnd, IDC_LGFEDIT, cfg.logfilename.path);
-               }
-               break;
-             case IDC_LSTATOFF:
-             case IDC_LSTATASCII:
-             case IDC_LSTATRAW:
-             case IDC_LSTATPACKET:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   if (IsDlgButtonChecked(hwnd, IDC_LSTATOFF))
-                       cfg.logtype = LGTYP_NONE;
-                   if (IsDlgButtonChecked(hwnd, IDC_LSTATASCII))
-                       cfg.logtype = LGTYP_ASCII;
-                   if (IsDlgButtonChecked(hwnd, IDC_LSTATRAW))
-                       cfg.logtype = LGTYP_DEBUG;
-                   if (IsDlgButtonChecked(hwnd, IDC_LSTATPACKET))
-                       cfg.logtype = LGTYP_PACKETS;
-               }
-               break;
-             case IDC_LSTATXASK:
-             case IDC_LSTATXAPN:
-             case IDC_LSTATXOVR:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   if (IsDlgButtonChecked(hwnd, IDC_LSTATXASK))
-                       cfg.logxfovr = LGXF_ASK;
-                   if (IsDlgButtonChecked(hwnd, IDC_LSTATXAPN))
-                       cfg.logxfovr = LGXF_APN;
-                   if (IsDlgButtonChecked(hwnd, IDC_LSTATXOVR))
-                       cfg.logxfovr = LGXF_OVR;
-               }
-               break;
-             case IDC_TSEDIT:
-             case IDC_R_TSEDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   GetDlgItemText(hwnd, LOWORD(wParam), cfg.termspeed,
-                                  sizeof(cfg.termspeed) - 1);
-               break;
-             case IDC_LOGEDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   GetDlgItemText(hwnd, IDC_LOGEDIT, cfg.username,
-                                  sizeof(cfg.username) - 1);
-               break;
-             case IDC_RLLUSEREDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   GetDlgItemText(hwnd, IDC_RLLUSEREDIT,
-                                  cfg.localusername,
-                                  sizeof(cfg.localusername) - 1);
-               break;
-             case IDC_EMBSD:
-             case IDC_EMRFC:
-               cfg.rfc_environ = IsDlgButtonChecked(hwnd, IDC_EMRFC);
-               break;
-             case IDC_TPASSIVE:
-             case IDC_TACTIVE:
-               cfg.passive_telnet =
-                   IsDlgButtonChecked(hwnd, IDC_TPASSIVE);
-               break;
-             case IDC_ENVADD:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   char str[sizeof(cfg.environmt)];
-                   char *p;
-                   GetDlgItemText(hwnd, IDC_VAREDIT, str,
-                                  sizeof(str) - 1);
-                   if (!*str) {
-                       MessageBeep(0);
-                       break;
-                   }
-                   p = str + strlen(str);
-                   *p++ = '\t';
-                   GetDlgItemText(hwnd, IDC_VALEDIT, p,
-                                  sizeof(str) - 1 - (p - str));
-                   if (!*p) {
-                       MessageBeep(0);
-                       break;
-                   }
-                   p = cfg.environmt;
-                   while (*p) {
-                       while (*p)
-                           p++;
-                       p++;
-                   }
-                   if ((p - cfg.environmt) + strlen(str) + 2 <
-                       sizeof(cfg.environmt)) {
-                       strcpy(p, str);
-                       p[strlen(str) + 1] = '\0';
-                       SendDlgItemMessage(hwnd, IDC_ENVLIST, LB_ADDSTRING,
-                                          0, (LPARAM) str);
-                       SetDlgItemText(hwnd, IDC_VAREDIT, "");
-                       SetDlgItemText(hwnd, IDC_VALEDIT, "");
-                   } else {
-                       MessageBox(hwnd, "Environment too big",
-                                  "PuTTY Error", MB_OK | MB_ICONERROR);
-                   }
-               }
-               break;
-             case IDC_ENVREMOVE:
-               if (HIWORD(wParam) != BN_CLICKED &&
-                   HIWORD(wParam) != BN_DOUBLECLICKED) break;
-               i =
-                   SendDlgItemMessage(hwnd, IDC_ENVLIST, LB_GETCURSEL, 0,
-                                      0);
-               if (i == LB_ERR)
-                   MessageBeep(0);
-               else {
-                   char *p, *q;
-
-                   SendDlgItemMessage(hwnd, IDC_ENVLIST, LB_DELETESTRING,
-                                      i, 0);
-                   p = cfg.environmt;
-                   while (i > 0) {
-                       if (!*p)
-                           goto disaster;
-                       while (*p)
-                           p++;
-                       p++;
-                       i--;
-                   }
-                   q = p;
-                   if (!*p)
-                       goto disaster;
-                   while (*p)
-                       p++;
-                   p++;
-                   while (*p) {
-                       while (*p)
-                           *q++ = *p++;
-                       *q++ = *p++;
-                   }
-                   *q = '\0';
-                 disaster:;
-               }
-               break;
-             case IDC_NOPTY:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.nopty = IsDlgButtonChecked(hwnd, IDC_NOPTY);
-               break;
-             case IDC_COMPRESS:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.compression =
-                       IsDlgButtonChecked(hwnd, IDC_COMPRESS);
-               break;
-             case IDC_SSH2DES:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.ssh2_des_cbc =
-                       IsDlgButtonChecked(hwnd, IDC_SSH2DES);
-               break;
-             case IDC_AGENTFWD:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.agentfwd =
-                       IsDlgButtonChecked(hwnd, IDC_AGENTFWD);
-               break;
-             case IDC_CHANGEUSER:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.change_username =
-                       IsDlgButtonChecked(hwnd, IDC_CHANGEUSER);
-               break;
-             case IDC_CIPHERLIST:
-             case IDC_CIPHERUP:
-             case IDC_CIPHERDN:
-               handle_prefslist(&cipherlist,
-                                cfg.ssh_cipherlist, CIPHER_MAX,
-                                0, hwnd, wParam, lParam);
-               break;
-             case IDC_SSHPROT1ONLY:
-             case IDC_SSHPROT1:
-             case IDC_SSHPROT2:
-             case IDC_SSHPROT2ONLY:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   if (IsDlgButtonChecked(hwnd, IDC_SSHPROT1ONLY))
-                       cfg.sshprot = 0;
-                   if (IsDlgButtonChecked(hwnd, IDC_SSHPROT1))
-                       cfg.sshprot = 1;
-                   else if (IsDlgButtonChecked(hwnd, IDC_SSHPROT2))
-                       cfg.sshprot = 2;
-                   else if (IsDlgButtonChecked(hwnd, IDC_SSHPROT2ONLY))
-                       cfg.sshprot = 3;
-               }
-               break;
-             case IDC_AUTHTIS:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.try_tis_auth =
-                       IsDlgButtonChecked(hwnd, IDC_AUTHTIS);
-               break;
-             case IDC_AUTHKI:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.try_ki_auth =
-                       IsDlgButtonChecked(hwnd, IDC_AUTHKI);
-               break;
-             case IDC_PKEDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   GetDlgItemText(hwnd, IDC_PKEDIT, cfg.keyfile.path,
-                                  sizeof(cfg.keyfile.path) - 1);
-               break;
-             case IDC_CMDEDIT:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   GetDlgItemText(hwnd, IDC_CMDEDIT, cfg.remote_cmd,
-                                  sizeof(cfg.remote_cmd) - 1);
-               break;
-             case IDC_PKBUTTON:
-               memset(&of, 0, sizeof(of));
-#ifdef OPENFILENAME_SIZE_VERSION_400
-               of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
-#else
-               of.lStructSize = sizeof(of);
-#endif
-               of.hwndOwner = hwnd;
-               of.lpstrFilter = "PuTTY Private Key Files (*.ppk)\0*.ppk\0"
-                   "All Files (*.*)\0*\0\0\0";
-               of.lpstrCustomFilter = NULL;
-               of.nFilterIndex = 1;
-               of.lpstrFile = filename;
-               strcpy(filename, cfg.keyfile.path);
-               of.nMaxFile = sizeof(filename);
-               of.lpstrFileTitle = NULL;
-               of.lpstrInitialDir = NULL;
-               of.lpstrTitle = "Select Private Key File";
-               of.Flags = 0;
-               if (GetOpenFileName(&of)) {
-                   strcpy(cfg.keyfile.path, filename);
-                   SetDlgItemText(hwnd, IDC_PKEDIT, cfg.keyfile.path);
-               }
-               break;
-             case IDC_RAWCNP:
-               cfg.rawcnp = IsDlgButtonChecked(hwnd, IDC_RAWCNP);
-               break;
-             case IDC_RTFPASTE:
-               cfg.rtf_paste = IsDlgButtonChecked(hwnd, IDC_RTFPASTE);
-               break;
-             case IDC_MBWINDOWS:
-             case IDC_MBXTERM:
-               cfg.mouse_is_xterm = IsDlgButtonChecked(hwnd, IDC_MBXTERM);
-               break;
-             case IDC_SELTYPELEX:
-             case IDC_SELTYPERECT:
-               cfg.rect_select = IsDlgButtonChecked(hwnd, IDC_SELTYPERECT);
-               break;
-             case IDC_MOUSEOVERRIDE:
-               cfg.mouse_override = IsDlgButtonChecked(hwnd, IDC_MOUSEOVERRIDE);
-               break;
-             case IDC_CCSET:
-               {
-                   BOOL ok;
-                   int i;
-                   int n = GetDlgItemInt(hwnd, IDC_CCEDIT, &ok, FALSE);
-
-                   if (!ok)
-                       MessageBeep(0);
-                   else {
-                       for (i = 0; i < 128; i++)
-                           if (SendDlgItemMessage
-                               (hwnd, IDC_CCLIST, LB_GETSEL, i, 0)) {
-                               char str[100];
-                               cfg.wordness[i] = n;
-                               SendDlgItemMessage(hwnd, IDC_CCLIST,
-                                                  LB_DELETESTRING, i, 0);
-                               sprintf(str, "%d\t(0x%02X)\t%c\t%d", i, i,
-                                       (i >= 0x21 && i != 0x7F) ? i : ' ',
-                                       cfg.wordness[i]);
-                               SendDlgItemMessage(hwnd, IDC_CCLIST,
-                                                  LB_INSERTSTRING, i,
-                                                  (LPARAM) str);
-                           }
-                   }
-               }
-               break;
-             case IDC_BOLDCOLOUR:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   int n, i;
-                   cfg.bold_colour =
-                       IsDlgButtonChecked(hwnd, IDC_BOLDCOLOUR);
-               }
-               break;
-             case IDC_PALETTE:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                       cfg.try_palette =
-                       IsDlgButtonChecked(hwnd, IDC_PALETTE);
-               break;
-             case IDC_COLOURLIST:
-               if (HIWORD(wParam) == LBN_DBLCLK ||
-                   HIWORD(wParam) == LBN_SELCHANGE) {
-                   int i =
-                       SendDlgItemMessage(hwnd, IDC_COLOURLIST,
-                                          LB_GETCURSEL,
-                                          0, 0);
-                   if (!cfg.bold_colour)
-                       i = (i < 3 ? i * 2 : i == 3 ? 5 : i * 2 - 2);
-                   SetDlgItemInt(hwnd, IDC_RVALUE, cfg.colours[i][0],
-                                 FALSE);
-                   SetDlgItemInt(hwnd, IDC_GVALUE, cfg.colours[i][1],
-                                 FALSE);
-                   SetDlgItemInt(hwnd, IDC_BVALUE, cfg.colours[i][2],
-                                 FALSE);
-               }
-               break;
-             case IDC_CHANGE:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   static CHOOSECOLOR cc;
-                   static DWORD custom[16] = { 0 };    /* zero initialisers */
-                   int i =
-                       SendDlgItemMessage(hwnd, IDC_COLOURLIST,
-                                          LB_GETCURSEL,
-                                          0, 0);
-                   if (!cfg.bold_colour)
-                       i = (i < 3 ? i * 2 : i == 3 ? 5 : i * 2 - 2);
-                   cc.lStructSize = sizeof(cc);
-                   cc.hwndOwner = hwnd;
-                   cc.hInstance = (HWND) hinst;
-                   cc.lpCustColors = custom;
-                   cc.rgbResult =
-                       RGB(cfg.colours[i][0], cfg.colours[i][1],
-                           cfg.colours[i][2]);
-                   cc.Flags = CC_FULLOPEN | CC_RGBINIT;
-                   if (ChooseColor(&cc)) {
-                       cfg.colours[i][0] =
-                           (unsigned char) (cc.rgbResult & 0xFF);
-                       cfg.colours[i][1] =
-                           (unsigned char) (cc.rgbResult >> 8) & 0xFF;
-                       cfg.colours[i][2] =
-                           (unsigned char) (cc.rgbResult >> 16) & 0xFF;
-                       SetDlgItemInt(hwnd, IDC_RVALUE, cfg.colours[i][0],
-                                     FALSE);
-                       SetDlgItemInt(hwnd, IDC_GVALUE, cfg.colours[i][1],
-                                     FALSE);
-                       SetDlgItemInt(hwnd, IDC_BVALUE, cfg.colours[i][2],
-                                     FALSE);
-                   }
-               }
-               break;
-             case IDC_CODEPAGE:
-               if (HIWORD(wParam) == CBN_SELCHANGE) {
-                   int index = SendDlgItemMessage(hwnd, IDC_CODEPAGE,
-                                                  CB_GETCURSEL, 0, 0);
-                   SendDlgItemMessage(hwnd, IDC_CODEPAGE, CB_GETLBTEXT,
-                                      index, (LPARAM)cfg.line_codepage);
-               } else if (HIWORD(wParam) == CBN_EDITCHANGE) {
-                   GetDlgItemText(hwnd, IDC_CODEPAGE, cfg.line_codepage,
-                                  sizeof(cfg.line_codepage) - 1);
-               } else if (HIWORD(wParam) == CBN_KILLFOCUS) {
-                   strcpy(cfg.line_codepage,
-                          cp_name(decode_codepage(cfg.line_codepage)));
-                   SetDlgItemText(hwnd, IDC_CODEPAGE, cfg.line_codepage);
-               }
-               break;
-             case IDC_PRINTER:
-               if (HIWORD(wParam) == CBN_SELCHANGE) {
-                   int index = SendDlgItemMessage(hwnd, IDC_PRINTER,
-                                                  CB_GETCURSEL, 0, 0);
-                   SendDlgItemMessage(hwnd, IDC_PRINTER, CB_GETLBTEXT,
-                                      index, (LPARAM)cfg.printer);
-               } else if (HIWORD(wParam) == CBN_EDITCHANGE) {
-                   GetDlgItemText(hwnd, IDC_PRINTER, cfg.printer,
-                                  sizeof(cfg.printer) - 1);
-               }
-               if (!strcmp(cfg.printer, PRINTER_DISABLED_STRING))
-                   *cfg.printer = '\0';
-               break;
-             case IDC_CAPSLOCKCYR:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   cfg.xlat_capslockcyr =
-                       IsDlgButtonChecked (hwnd, IDC_CAPSLOCKCYR);
-               }
-               break;
-             case IDC_VTXWINDOWS:
-             case IDC_VTOEMANSI:
-             case IDC_VTOEMONLY:
-             case IDC_VTPOORMAN:
-             case IDC_VTUNICODE:
-               cfg.vtmode =
-                   (IsDlgButtonChecked(hwnd, IDC_VTXWINDOWS) ? VT_XWINDOWS
-                    : IsDlgButtonChecked(hwnd,
-                                         IDC_VTOEMANSI) ? VT_OEMANSI :
-                    IsDlgButtonChecked(hwnd,
-                                       IDC_VTOEMONLY) ? VT_OEMONLY :
-                    IsDlgButtonChecked(hwnd,
-                                       IDC_VTUNICODE) ? VT_UNICODE :
-                    VT_POORMAN);
-               break;
-             case IDC_X11_FORWARD:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                   cfg.x11_forward =
-                   IsDlgButtonChecked(hwnd, IDC_X11_FORWARD);
-               break;
-             case IDC_LPORT_ALL:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                   cfg.lport_acceptall =
-                   IsDlgButtonChecked(hwnd, IDC_LPORT_ALL);
-               break;
-             case IDC_RPORT_ALL:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED)
-                   cfg.rport_acceptall =
-                   IsDlgButtonChecked(hwnd, IDC_RPORT_ALL);
-               break;
-             case IDC_X11_DISPLAY:
-               if (HIWORD(wParam) == EN_CHANGE)
-                   GetDlgItemText(hwnd, IDC_X11_DISPLAY, cfg.x11_display,
-                                  sizeof(cfg.x11_display) - 1);
-               break;
-             case IDC_X11MIT:
-             case IDC_X11XDM:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   if (IsDlgButtonChecked(hwnd, IDC_X11MIT))
-                       cfg.x11_auth = X11_MIT;
-                   else if (IsDlgButtonChecked(hwnd, IDC_X11XDM))
-                       cfg.x11_auth = X11_XDM;
-               }
-               break;
-             case IDC_PFWDADD:
-               if (HIWORD(wParam) == BN_CLICKED ||
-                   HIWORD(wParam) == BN_DOUBLECLICKED) {
-                   char str[sizeof(cfg.portfwd)];
-                   char *p;
-                   if (IsDlgButtonChecked(hwnd, IDC_PFWDLOCAL))
-                       str[0] = 'L';
-                   else
-                       str[0] = 'R';
-                   GetDlgItemText(hwnd, IDC_SPORTEDIT, str+1,
-                                  sizeof(str) - 2);
-                   if (!str[1]) {
-                       MessageBox(hwnd,
-                                  "You need to specify a source port number",
-                                  "PuTTY Error", MB_OK | MB_ICONERROR);
-                       break;
-                   }
-                   p = str + strlen(str);
-                   *p++ = '\t';
-                   GetDlgItemText(hwnd, IDC_DPORTEDIT, p,
-                                  sizeof(str) - 1 - (p - str));
-                   if (!*p || !strchr(p, ':')) {
-                       MessageBox(hwnd,
-                                  "You need to specify a destination address\n"
-                                  "in the form \"host.name:port\"",
-                                  "PuTTY Error", MB_OK | MB_ICONERROR);
-                       break;
-                   }
-                   p = cfg.portfwd;
-                   while (*p) {
-                       while (*p)
-                           p++;
-                       p++;
-                   }
-                   if ((p - cfg.portfwd) + strlen(str) + 2 <
-                       sizeof(cfg.portfwd)) {
-                       strcpy(p, str);
-                       p[strlen(str) + 1] = '\0';
-                       SendDlgItemMessage(hwnd, IDC_PFWDLIST, LB_ADDSTRING,
-                                          0, (LPARAM) str);
-                       SetDlgItemText(hwnd, IDC_SPORTEDIT, "");
-                       SetDlgItemText(hwnd, IDC_DPORTEDIT, "");
-                   } else {
-                       MessageBox(hwnd, "Too many forwardings",
-                                  "PuTTY Error", MB_OK | MB_ICONERROR);
-                   }
-               }
-               break;
-             case IDC_PFWDREMOVE:
-               if (HIWORD(wParam) != BN_CLICKED &&
-                   HIWORD(wParam) != BN_DOUBLECLICKED) break;
-               i = SendDlgItemMessage(hwnd, IDC_PFWDLIST,
-                                      LB_GETCURSEL, 0, 0);
-               if (i == LB_ERR)
-                   MessageBeep(0);
-               else {
-                   char *p, *q;
-
-                   SendDlgItemMessage(hwnd, IDC_PFWDLIST, LB_DELETESTRING,
-                                      i, 0);
-                   p = cfg.portfwd;
-                   while (i > 0) {
-                       if (!*p)
-                           goto disaster2;
-                       while (*p)
-                           p++;
-                       p++;
-                       i--;
-                   }
-                   q = p;
-                   if (!*p)
-                       goto disaster2;
-                   while (*p)
-                       p++;
-                   p++;
-                   while (*p) {
-                       while (*p)
-                           *q++ = *p++;
-                       *q++ = *p++;
-                   }
-                   *q = '\0';
-                 disaster2:;
-               }
-               break;
-             case IDC_BUGD_IGNORE1:
-               if (HIWORD(wParam) == CBN_SELCHANGE) {
-                   int index = SendDlgItemMessage(hwnd, IDC_BUGD_IGNORE1,
-                                                  CB_GETCURSEL, 0, 0);
-                   cfg.sshbug_ignore1 = (index == 0 ? AUTO :
-                                         index == 1 ? FORCE_OFF : FORCE_ON);
-               }
-               break;
-             case IDC_BUGD_PLAINPW1:
-               if (HIWORD(wParam) == CBN_SELCHANGE) {
-                   int index = SendDlgItemMessage(hwnd, IDC_BUGD_PLAINPW1,
-                                                  CB_GETCURSEL, 0, 0);
-                   cfg.sshbug_plainpw1 = (index == 0 ? AUTO :
-                                          index == 1 ? FORCE_OFF : FORCE_ON);
-               }
-               break;
-             case IDC_BUGD_RSA1:
-               if (HIWORD(wParam) == CBN_SELCHANGE) {
-                   int index = SendDlgItemMessage(hwnd, IDC_BUGD_RSA1,
-                                                  CB_GETCURSEL, 0, 0);
-                   cfg.sshbug_rsa1 = (index == 0 ? AUTO :
-                                      index == 1 ? FORCE_OFF : FORCE_ON);
-               }
-               break;
-             case IDC_BUGD_HMAC2:
-               if (HIWORD(wParam) == CBN_SELCHANGE) {
-                   int index = SendDlgItemMessage(hwnd, IDC_BUGD_HMAC2,
-                                                  CB_GETCURSEL, 0, 0);
-                   cfg.sshbug_hmac2 = (index == 0 ? AUTO :
-                                       index == 1 ? FORCE_OFF : FORCE_ON);
-               }
-               break;
-             case IDC_BUGD_DERIVEKEY2:
-               if (HIWORD(wParam) == CBN_SELCHANGE) {
-                   int index = SendDlgItemMessage(hwnd, IDC_BUGD_DERIVEKEY2,
-                                                  CB_GETCURSEL, 0, 0);
-                   cfg.sshbug_derivekey2 = (index == 0 ? AUTO :
-                                            index == 1 ? FORCE_OFF:FORCE_ON);
-               }
-               break;
-             case IDC_BUGD_RSAPAD2:
-               if (HIWORD(wParam) == CBN_SELCHANGE) {
-                   int index = SendDlgItemMessage(hwnd, IDC_BUGD_RSAPAD2,
-                                                  CB_GETCURSEL, 0, 0);
-                   cfg.sshbug_rsapad2 = (index == 0 ? AUTO :
-                                         index == 1 ? FORCE_OFF : FORCE_ON);
-               }
-               break;
-             case IDC_BUGD_DHGEX2:
-               if (HIWORD(wParam) == CBN_SELCHANGE) {
-                   int index = SendDlgItemMessage(hwnd, IDC_BUGD_DHGEX2,
-                                                  CB_GETCURSEL, 0, 0);
-                   cfg.sshbug_dhgex2 = (index == 0 ? AUTO :
-                                        index == 1 ? FORCE_OFF : FORCE_ON);
-               }
-               break;
-             case IDC_BUGD_PKSESSID2:
-               if (HIWORD(wParam) == CBN_SELCHANGE) {
-                   int index = SendDlgItemMessage(hwnd, IDC_BUGD_PKSESSID2,
-                                                  CB_GETCURSEL, 0, 0);
-                   cfg.sshbug_pksessid2 = (index == 0 ? AUTO :
-                                            index == 1 ? FORCE_OFF : FORCE_ON);
-               }
-               break;
-           }
-       return 0;
+       if (GetWindowLong(hwnd, GWL_USERDATA) == 1) {
+           ret = winctrl_handle_command(&dp, msg, wParam, lParam);
+           if (dp.ended && GetCapture() != hwnd)
+               EndDialog(hwnd, dp.endresult ? 1 : 0);
+       } else
+           ret = 0;
+       return ret;
       case WM_HELP:
         if (help_path) {
-            int id = ((LPHELPINFO)lParam)->iCtrlId;
-            char *cmd = help_context_cmd(id);
-            if (cmd) {
-                WinHelp(hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
+           if (winctrl_context_help(&dp, hwnd,
+                                    ((LPHELPINFO)lParam)->iCtrlId))
                 requested_help = TRUE;
-            } else {
+           else
                 MessageBeep(0);
-            }
         }
         break;
       case WM_CLOSE:
@@ -3728,47 +536,26 @@ static int GenericMainDlgProc(HWND hwnd, UINT msg,
            force_normal(hwnd);
        return 0;
 
-      default:
-       /*
-        * Handle application-defined messages eg. DragListBox
-        */
-       /* First find out what the number is (once). */
-       if (draglistmsg == WM_NULL)
-           draglistmsg = RegisterWindowMessage (DRAGLISTMSGSTRING);
-
-       if (msg == draglistmsg) {
-           /* Only process once dialog is fully formed. */
-           if (GetWindowLong(hwnd, GWL_USERDATA) == 1) switch (LOWORD(wParam)) {
-             case IDC_CIPHERLIST:
-               return handle_prefslist(&cipherlist,
-                                       cfg.ssh_cipherlist, CIPHER_MAX,
-                                       1, hwnd, wParam, lParam);
-           }
-       }
-       return 0;
-
     }
     return 0;
 }
 
-static int CALLBACK MainDlgProc(HWND hwnd, UINT msg,
-                               WPARAM wParam, LPARAM lParam)
+void modal_about_box(HWND hwnd)
 {
-    if (msg == WM_COMMAND && LOWORD(wParam) == IDOK) {
-    }
-    if (msg == WM_COMMAND && LOWORD(wParam) == IDCX_ABOUT) {
-       EnableWindow(hwnd, 0);
-       DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
-       EnableWindow(hwnd, 1);
-       SetActiveWindow(hwnd);
-    }
-    return GenericMainDlgProc(hwnd, msg, wParam, lParam, 0);
+    EnableWindow(hwnd, 0);
+    DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
+    EnableWindow(hwnd, 1);
+    SetActiveWindow(hwnd);
 }
 
-static int CALLBACK ReconfDlgProc(HWND hwnd, UINT msg,
-                                 WPARAM wParam, LPARAM lParam)
+void show_help(HWND hwnd)
 {
-    return GenericMainDlgProc(hwnd, msg, wParam, lParam, 1);
+    if (help_path) {
+       WinHelp(hwnd, help_path,
+               help_has_contents ? HELP_FINDER : HELP_CONTENTS,
+               0);
+       requested_help = TRUE;
+    }
 }
 
 void defuse_showwindow(void)
@@ -3792,12 +579,31 @@ int do_config(void)
 {
     int ret;
 
+    ctrlbox = ctrl_new_box();
+    setup_config_box(ctrlbox, &sesslist, FALSE, 0);
+    win_setup_config_box(ctrlbox, &dp.hwnd, (help_path != NULL), FALSE);
+    winctrl_init(&ctrls_base);
+    winctrl_init(&ctrls_panel);
+    dp.controltrees[0] = &ctrls_base;
+    dp.controltrees[1] = &ctrls_panel;
+    dp.nctrltrees = 2;
+    dp.errtitle = "PuTTY Error";
+    dp.data = &cfg;
+    dp.ended = FALSE;
+    dp.lastfocused = NULL;
+    memset(dp.shortcuts, 0, sizeof(dp.shortcuts));
+    dp.shortcuts['g'] = TRUE;         /* the treeview: `Cate&gory' */
+
     get_sesslist(&sesslist, TRUE);
-    savedsession[0] = '\0';
     ret =
-       DialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL, MainDlgProc);
+       DialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
+                 GenericMainDlgProc);
     get_sesslist(&sesslist, FALSE);
 
+    ctrl_free_box(ctrlbox);
+    winctrl_cleanup(&ctrls_base);
+    winctrl_cleanup(&ctrls_panel);
+
     return ret;
 }
 
@@ -3807,8 +613,30 @@ int do_reconfig(HWND hwnd)
     int ret;
 
     backup_cfg = cfg;                 /* structure copy */
+
+    ctrlbox = ctrl_new_box();
+    setup_config_box(ctrlbox, NULL, TRUE, cfg.protocol);
+    win_setup_config_box(ctrlbox, &dp.hwnd, (help_path != NULL), TRUE);
+    winctrl_init(&ctrls_base);
+    winctrl_init(&ctrls_panel);
+    dp.controltrees[0] = &ctrls_base;
+    dp.controltrees[1] = &ctrls_panel;
+    dp.nctrltrees = 2;
+    dp.errtitle = "PuTTY Error";
+    dp.data = &cfg;
+    dp.ended = FALSE;
+    dp.lastfocused = NULL;
+    memset(dp.shortcuts, 0, sizeof(dp.shortcuts));
+    dp.shortcuts['g'] = TRUE;         /* the treeview: `Cate&gory' */
+
     ret =
-       DialogBox(hinst, MAKEINTRESOURCE(IDD_RECONF), hwnd, ReconfDlgProc);
+       DialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
+                 GenericMainDlgProc);
+
+    ctrl_free_box(ctrlbox);
+    winctrl_cleanup(&ctrls_base);
+    winctrl_cleanup(&ctrls_panel);
+
     if (!ret)
        cfg = backup_cfg;              /* structure copy */
 
diff --git a/winhelp.h b/winhelp.h
new file mode 100644 (file)
index 0000000..04e1214
--- /dev/null
+++ b/winhelp.h
@@ -0,0 +1,109 @@
+/*
+ * winhelp.h - define Windows Help context names for the controls
+ * in the PuTTY config box.
+ */
+
+#define HELPCTX(x) P(WINHELP_CTX_ ## x)
+
+#define WINHELP_CTX_no_help NULL
+
+#define WINHELP_CTX_session_hostname "session.hostname"
+#define WINHELP_CTX_session_saved "session.saved"
+#define WINHELP_CTX_session_coe "session.coe"
+#define WINHELP_CTX_logging_main "logging.main"
+#define WINHELP_CTX_logging_filename "logging.filename"
+#define WINHELP_CTX_logging_exists "logging.exists"
+#define WINHELP_CTX_keyboard_backspace "keyboard.backspace"
+#define WINHELP_CTX_keyboard_homeend "keyboard.homeend"
+#define WINHELP_CTX_keyboard_funkeys "keyboard.funkeys"
+#define WINHELP_CTX_keyboard_appkeypad "keyboard.appkeypad"
+#define WINHELP_CTX_keyboard_appcursor "keyboard.appcursor"
+#define WINHELP_CTX_keyboard_nethack "keyboard.nethack"
+#define WINHELP_CTX_keyboard_compose "keyboard.compose"
+#define WINHELP_CTX_keyboard_ctrlalt "keyboard.ctrlalt"
+#define WINHELP_CTX_features_application "features.application"
+#define WINHELP_CTX_features_mouse "features.mouse"
+#define WINHELP_CTX_features_resize "features.resize"
+#define WINHELP_CTX_features_altscreen "features.altscreen"
+#define WINHELP_CTX_features_retitle "features.retitle"
+#define WINHELP_CTX_features_dbackspace "features.dbackspace"
+#define WINHELP_CTX_features_charset "features.charset"
+#define WINHELP_CTX_terminal_autowrap "terminal.autowrap"
+#define WINHELP_CTX_terminal_decom "terminal.decom"
+#define WINHELP_CTX_terminal_lfhascr "terminal.lfhascr"
+#define WINHELP_CTX_terminal_bce "terminal.bce"
+#define WINHELP_CTX_terminal_blink "terminal.blink"
+#define WINHELP_CTX_terminal_answerback "terminal.answerback"
+#define WINHELP_CTX_terminal_localecho "terminal.localecho"
+#define WINHELP_CTX_terminal_localedit "terminal.localedit"
+#define WINHELP_CTX_terminal_printing "terminal.printing"
+#define WINHELP_CTX_bell_style "bell.style"
+#define WINHELP_CTX_bell_taskbar "bell.taskbar"
+#define WINHELP_CTX_bell_overload "bell.overload"
+#define WINHELP_CTX_window_size "window.size"
+#define WINHELP_CTX_window_resize "window.resize"
+#define WINHELP_CTX_window_scrollback "window.scrollback"
+#define WINHELP_CTX_behaviour_closewarn "behaviour.closewarn"
+#define WINHELP_CTX_behaviour_altf4 "behaviour.altf4"
+#define WINHELP_CTX_behaviour_altspace "behaviour.altspace"
+#define WINHELP_CTX_behaviour_altonly "behaviour.altonly"
+#define WINHELP_CTX_behaviour_alwaysontop "behaviour.alwaysontop"
+#define WINHELP_CTX_behaviour_altenter "behaviour.altenter"
+#define WINHELP_CTX_appearance_cursor "appearance.cursor"
+#define WINHELP_CTX_appearance_font "appearance.font"
+#define WINHELP_CTX_appearance_title "appearance.title"
+#define WINHELP_CTX_appearance_hidemouse "appearance.hidemouse"
+#define WINHELP_CTX_appearance_border "appearance.border"
+#define WINHELP_CTX_connection_termtype "connection.termtype"
+#define WINHELP_CTX_connection_username "connection.username"
+#define WINHELP_CTX_connection_keepalive "connection.keepalive"
+#define WINHELP_CTX_connection_nodelay "connection.nodelay"
+#define WINHELP_CTX_proxy_type "proxy.type"
+#define WINHELP_CTX_proxy_main "proxy.main"
+#define WINHELP_CTX_proxy_exclude "proxy.exclude"
+#define WINHELP_CTX_proxy_dns "proxy.dns"
+#define WINHELP_CTX_proxy_auth "proxy.auth"
+#define WINHELP_CTX_proxy_command "proxy.command"
+#define WINHELP_CTX_proxy_socksver "proxy.socksver"
+#define WINHELP_CTX_telnet_termspeed "telnet.termspeed"
+#define WINHELP_CTX_telnet_environ "telnet.environ"
+#define WINHELP_CTX_telnet_oldenviron "telnet.oldenviron"
+#define WINHELP_CTX_telnet_passive "telnet.passive"
+#define WINHELP_CTX_telnet_specialkeys "telnet.specialkeys"
+#define WINHELP_CTX_telnet_newline "telnet.newline"
+#define WINHELP_CTX_rlogin_termspeed "rlogin.termspeed"
+#define WINHELP_CTX_rlogin_localuser "rlogin.localuser"
+#define WINHELP_CTX_ssh_nopty "ssh.nopty"
+#define WINHELP_CTX_ssh_ciphers "ssh.ciphers"
+#define WINHELP_CTX_ssh_protocol "ssh.protocol"
+#define WINHELP_CTX_ssh_command "ssh.command"
+#define WINHELP_CTX_ssh_compress "ssh.compress"
+#define WINHELP_CTX_ssh_auth_privkey "ssh.auth.privkey"
+#define WINHELP_CTX_ssh_auth_agentfwd "ssh.auth.agentfwd"
+#define WINHELP_CTX_ssh_auth_changeuser "ssh.auth.changeuser"
+#define WINHELP_CTX_ssh_auth_tis "ssh.auth.tis"
+#define WINHELP_CTX_ssh_auth_ki "ssh.auth.ki"
+#define WINHELP_CTX_selection_buttons "selection.buttons"
+#define WINHELP_CTX_selection_shiftdrag "selection.shiftdrag"
+#define WINHELP_CTX_selection_rect "selection.rect"
+#define WINHELP_CTX_selection_charclasses "selection.charclasses"
+#define WINHELP_CTX_selection_linedraw "selection.linedraw"
+#define WINHELP_CTX_selection_rtf "selection.rtf"
+#define WINHELP_CTX_colours_bold "colours.bold"
+#define WINHELP_CTX_colours_logpal "colours.logpal"
+#define WINHELP_CTX_colours_config "colours.config"
+#define WINHELP_CTX_translation_codepage "translation.codepage"
+#define WINHELP_CTX_translation_cyrillic "translation.cyrillic"
+#define WINHELP_CTX_translation_linedraw "translation.linedraw"
+#define WINHELP_CTX_ssh_tunnels_x11 "ssh.tunnels.x11"
+#define WINHELP_CTX_ssh_tunnels_x11auth "ssh.tunnels.x11auth"
+#define WINHELP_CTX_ssh_tunnels_portfwd "ssh.tunnels.portfwd"
+#define WINHELP_CTX_ssh_tunnels_portfwd_localhost "ssh.tunnels.portfwd.localhost"
+#define WINHELP_CTX_ssh_bugs_ignore1 "ssh.bugs.ignore1"
+#define WINHELP_CTX_ssh_bugs_plainpw1 "ssh.bugs.plainpw1"
+#define WINHELP_CTX_ssh_bugs_rsa1 "ssh.bugs.rsa1"
+#define WINHELP_CTX_ssh_bugs_hmac2 "ssh.bugs.hmac2"
+#define WINHELP_CTX_ssh_bugs_derivekey2 "ssh.bugs.derivekey2"
+#define WINHELP_CTX_ssh_bugs_rsapad2 "ssh.bugs.rsapad2"
+#define WINHELP_CTX_ssh_bugs_dhgex2 "ssh.bugs.dhgex2"
+#define WINHELP_CTX_ssh_bugs_pksessid2 "ssh.bugs.pksessid2"
index 1cbe5b7..21d7aa8 100644 (file)
@@ -7,6 +7,10 @@
 
 #include <stdio.h>                    /* for FILENAME_MAX */
 
+#include "tree234.h"
+
+#include "winhelp.h"
+
 struct Filename {
     char path[FILENAME_MAX];
 };
@@ -112,6 +116,16 @@ GLOBAL void *logctx;
 #define sk_getxdmdata(socket, ip, port) (0)
 
 /*
+ * File-selector filter strings used in the config box. On Windows,
+ * these strings are of exactly the type needed to go in
+ * `lpstrFilter' in an OPENFILENAME structure.
+ */
+#define FILTER_KEY_FILES ("PuTTY Private Key Files (*.ppk)\0*.ppk\0" \
+                             "All Files (*.*)\0*\0\0\0")
+#define FILTER_WAVE_FILES ("Wave Files (*.wav)\0*.WAV\0" \
+                              "All Files (*.*)\0*\0\0\0")
+
+/*
  * Exports from winctrls.c.
  */
 
@@ -142,6 +156,24 @@ struct prefslist {
 };
 
 /*
+ * This structure is passed to event handler functions as the `dlg'
+ * parameter, and hence is passed back to winctrls access functions.
+ */
+struct dlgparam {
+    HWND hwnd;                        /* the hwnd of the dialog box */
+    struct winctrls *controltrees[8];  /* can have several of these */
+    int nctrltrees;
+    char *errtitle;                   /* title of error sub-messageboxes */
+    void *data;                               /* data to pass in refresh events */
+    union control *focused, *lastfocused; /* which ctrl has focus now/before */
+    int coloursel_wanted;             /* has an event handler asked for
+                                       * a colour selector? */
+    char shortcuts[128];              /* track which shortcuts in use */
+    struct { unsigned char r, g, b, ok; } coloursel_result;   /* 0-255 */
+    int ended, endresult;             /* has the dialog been ended? */
+};
+
+/*
  * Exports from winctrls.c.
  */
 void ctlposinit(struct ctlpos *cp, HWND hwnd,
@@ -151,7 +183,7 @@ HWND doctl(struct ctlpos *cp, RECT r,
 void bartitle(struct ctlpos *cp, char *name, int id);
 void beginbox(struct ctlpos *cp, char *name, int idbox);
 void endbox(struct ctlpos *cp);
-void multiedit(struct ctlpos *cp, ...);
+void multiedit(struct ctlpos *cp, int password, ...);
 void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...);
 void bareradioline(struct ctlpos *cp, int nacross, ...);
 void radiobig(struct ctlpos *cp, char *text, int id, ...);
@@ -183,8 +215,8 @@ void charclass(struct ctlpos *cp, char *stext, int sid, int listid,
               char *btext, int bid, int eid, char *s2text, int s2id);
 void colouredit(struct ctlpos *cp, char *stext, int sid, int listid,
                char *btext, int bid, ...);
-void prefslist(struct prefslist *hdl, struct ctlpos *cp, char *stext,
-              int sid, int listid, int upbid, int dnbid);
+void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines,
+              char *stext, int sid, int listid, int upbid, int dnbid);
 int handle_prefslist(struct prefslist *hdl,
                     int *array, int maxmemb,
                     int is_dlmsg, HWND hwnd,
@@ -196,6 +228,56 @@ void fwdsetter(struct ctlpos *cp, int listid, char *stext, int sid,
               char *btext, int bid,
               char *r1text, int r1id, char *r2text, int r2id);
 
+#define MAX_SHORTCUTS_PER_CTRL 16
+
+/*
+ * This structure is what's stored for each `union control' in the
+ * portable-dialog interface.
+ */
+struct winctrl {
+    union control *ctrl;
+    /*
+     * The control may have several components at the Windows
+     * level, with different dialog IDs. To avoid needing N
+     * separate platformsidectrl structures (which could be stored
+     * separately in a tree234 so that lookup by ID worked), we
+     * impose the constraint that those IDs must be in a contiguous
+     * block.
+     */
+    int base_id;
+    int num_ids;
+    /*
+     * Remember what keyboard shortcuts were used by this control,
+     * so that when we remove it again we can take them out of the
+     * list in the dlgparam.
+     */
+    char shortcuts[MAX_SHORTCUTS_PER_CTRL];
+    /*
+     * Some controls need a piece of allocated memory in which to
+     * store temporary data about the control.
+     */
+    void *data;
+};
+/*
+ * And this structure holds a set of the above, in two separate
+ * tree234s so that it can find an item by `union control' or by
+ * dialog ID.
+ */
+struct winctrls {
+    tree234 *byctrl, *byid;
+};
+void winctrl_init(struct winctrls *);
+void winctrl_cleanup(struct winctrls *);
+void winctrl_add(struct winctrls *, struct winctrl *);
+void winctrl_remove(struct winctrls *, struct winctrl *);
+struct winctrl *winctrl_findbyctrl(struct winctrls *, union control *);
+struct winctrl *winctrl_findbyid(struct winctrls *, int);
+struct winctrl *winctrl_findbyindex(struct winctrls *, int);
+void winctrl_layout(struct dlgparam *dp, struct winctrls *wc,
+                   struct ctlpos *cp, struct controlset *s, int *id);
+int winctrl_handle_command(struct dlgparam *dp, UINT msg,
+                          WPARAM wParam, LPARAM lParam);
+
 /*
  * Exports from windlg.c.
  */