Jacob's patch for a drag-list to select SSH ciphers. Heavily hacked
[u/mdw/putty] / winctrls.c
index 8ed1528..a098c98 100644 (file)
@@ -7,6 +7,9 @@
 #include <commctrl.h>
 
 #include "winstuff.h"
+#include "puttymem.h"
+
+#include "putty.h"
 
 #define GAPBETWEEN 3
 #define GAPWITHIN 1
@@ -22,7 +25,8 @@
 #define PROGBARHEIGHT 14
 
 void ctlposinit(struct ctlpos *cp, HWND hwnd,
-                int leftborder, int rightborder, int topborder) {
+               int leftborder, int rightborder, int topborder)
+{
     RECT r, r2;
     cp->hwnd = hwnd;
     cp->font = SendMessage(hwnd, WM_GETFONT, 0, 0);
@@ -33,14 +37,14 @@ void ctlposinit(struct ctlpos *cp, HWND hwnd,
     r2.bottom = 8;
     MapDialogRect(hwnd, &r2);
     cp->dlu4inpix = r2.right;
-    cp->width = (r.right * 4) / (r2.right) - 2*GAPBETWEEN;
+    cp->width = (r.right * 4) / (r2.right) - 2 * GAPBETWEEN;
     cp->xoff = leftborder;
     cp->width -= leftborder + rightborder;
 }
 
-void doctl(struct ctlpos *cp, RECT r,
-           char *wclass, int wstyle, int exstyle,
-           char *wtext, int wid) {
+HWND doctl(struct ctlpos *cp, RECT r,
+          char *wclass, int wstyle, int exstyle, char *wtext, int wid)
+{
     HWND ctl;
     /*
      * Note nonstandard use of RECT. This is deliberate: by
@@ -52,19 +56,23 @@ void doctl(struct ctlpos *cp, RECT r,
     MapDialogRect(cp->hwnd, &r);
 
     ctl = CreateWindowEx(exstyle, wclass, wtext, wstyle,
-                         r.left, r.top, r.right, r.bottom,
-                         cp->hwnd, (HMENU)wid, hinst, NULL);
+                        r.left, r.top, r.right, r.bottom,
+                        cp->hwnd, (HMENU) wid, hinst, NULL);
     SendMessage(ctl, WM_SETFONT, cp->font, MAKELPARAM(TRUE, 0));
+    return ctl;
 }
 
 /*
  * A title bar across the top of a sub-dialog.
  */
-void bartitle(struct ctlpos *cp, char *name, int id) {
+void bartitle(struct ctlpos *cp, char *name, int id)
+{
     RECT r;
 
-    r.left = GAPBETWEEN; r.right = cp->width;
-    r.top = cp->ypos; r.bottom = STATICHEIGHT;
+    r.left = GAPBETWEEN;
+    r.right = cp->width;
+    r.top = cp->ypos;
+    r.bottom = STATICHEIGHT;
     cp->ypos += r.bottom + GAPBETWEEN;
     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, name, id);
 }
@@ -72,49 +80,35 @@ void bartitle(struct ctlpos *cp, char *name, int id) {
 /*
  * Begin a grouping box, with or without a group title.
  */
-void beginbox(struct ctlpos *cp, char *name, int idbox, int idtext) {
-    if (name)
-        cp->ypos += STATICHEIGHT/2;
+void beginbox(struct ctlpos *cp, char *name, int idbox)
+{
     cp->boxystart = cp->ypos;
+    if (!name)
+       cp->boxystart -= STATICHEIGHT / 2;
     if (name)
-        cp->ypos += STATICHEIGHT - (STATICHEIGHT/2);
+       cp->ypos += STATICHEIGHT;
     cp->ypos += GAPYBOX;
-    cp->width -= 2*GAPXBOX;
+    cp->width -= 2 * GAPXBOX;
     cp->xoff += GAPXBOX;
     cp->boxid = idbox;
-    cp->boxtextid = idtext;
     cp->boxtext = name;
 }
 
 /*
  * End a grouping box.
  */
-void endbox(struct ctlpos *cp) {
+void endbox(struct ctlpos *cp)
+{
     RECT r;
     cp->xoff -= GAPXBOX;
-    cp->width += 2*GAPXBOX;
+    cp->width += 2 * GAPXBOX;
     cp->ypos += GAPYBOX - GAPBETWEEN;
-    r.left = GAPBETWEEN; r.right = cp->width;
-    r.top = cp->boxystart; r.bottom = cp->ypos - cp->boxystart;
-    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_ETCHEDFRAME, 0,
-          "", cp->boxid);
-    if (cp->boxtext) {
-        SIZE s;
-        HDC hdc;
-        HFONT oldfont, dlgfont;
-        hdc = GetDC(cp->hwnd);
-        dlgfont = (HFONT)cp->font;
-        oldfont = SelectObject(hdc, dlgfont);
-        GetTextExtentPoint32(hdc, cp->boxtext, strlen(cp->boxtext), &s);
-        SelectObject(hdc, oldfont);
-        DeleteDC(hdc);
-        r.left = GAPXBOX + GAPBETWEEN;
-        r.right = (s.cx * 4 + cp->dlu4inpix-1) / cp->dlu4inpix;
-        
-        r.top = cp->boxystart - STATICHEIGHT/2; r.bottom = STATICHEIGHT;
-        doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0,
-              cp->boxtext, cp->boxtextid);
-    }
+    r.left = GAPBETWEEN;
+    r.right = cp->width;
+    r.top = cp->boxystart;
+    r.bottom = cp->ypos - cp->boxystart;
+    doctl(cp, r, "BUTTON", BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 0,
+         cp->boxtext ? cp->boxtext : "", cp->boxid);
     cp->ypos += GAPYBOX;
 }
 
@@ -122,7 +116,8 @@ 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, ...)
+{
     RECT r;
     va_list ap;
     int percent, xpos;
@@ -130,31 +125,94 @@ void multiedit(struct ctlpos *cp, ...) {
     percent = xpos = 0;
     va_start(ap, cp);
     while (1) {
-        char *text;
-        int staticid, editid, pcwidth;
-        text = va_arg(ap, char *);
-        if (!text)
-            break;
-        staticid = va_arg(ap, int);
-        editid = va_arg(ap, int);
-        pcwidth = va_arg(ap, int);
-
-        r.left = xpos + GAPBETWEEN;
-        percent += pcwidth;
-        xpos = (cp->width + GAPBETWEEN) * percent / 100;
-        r.right = xpos - r.left;
-
-        r.top = cp->ypos; r.bottom = STATICHEIGHT;
-        doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0,
-              text, staticid);
-        r.top = cp->ypos + 8 + GAPWITHIN; r.bottom = EDITHEIGHT;
-        doctl(cp, r, "EDIT",
-              WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
-              WS_EX_CLIENTEDGE,
-              "", editid);
+       char *text;
+       int staticid, editid, pcwidth;
+       text = va_arg(ap, char *);
+       if (!text)
+           break;
+       staticid = va_arg(ap, int);
+       editid = va_arg(ap, int);
+       pcwidth = va_arg(ap, int);
+
+       r.left = xpos + GAPBETWEEN;
+       percent += pcwidth;
+       xpos = (cp->width + GAPBETWEEN) * percent / 100;
+       r.right = xpos - r.left;
+
+       r.top = cp->ypos;
+       r.bottom = STATICHEIGHT;
+       doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, staticid);
+       r.top = cp->ypos + 8 + GAPWITHIN;
+       r.bottom = EDITHEIGHT;
+       doctl(cp, r, "EDIT",
+             WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
+             WS_EX_CLIENTEDGE, "", editid);
     }
     va_end(ap);
-    cp->ypos += 8+GAPWITHIN+12+GAPBETWEEN;
+    cp->ypos += STATICHEIGHT + GAPWITHIN + EDITHEIGHT + GAPBETWEEN;
+}
+
+/*
+ * A static line, followed by a full-width drop-down list (ie a
+ * non-editing combo box).
+ */
+void dropdownlist(struct ctlpos *cp, char *text, int staticid, int listid)
+{
+    RECT r;
+    va_list ap;
+
+    r.left = GAPBETWEEN;
+    r.right = cp->width;
+
+    r.top = cp->ypos;
+    r.bottom = STATICHEIGHT;
+    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, staticid);
+    r.top = cp->ypos + 8 + GAPWITHIN;
+    r.bottom = COMBOHEIGHT * 10;
+    doctl(cp, r, "COMBOBOX",
+         WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
+         CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", listid);
+
+    cp->ypos += STATICHEIGHT + GAPWITHIN + COMBOHEIGHT + GAPBETWEEN;
+}
+
+static void radioline_common(struct ctlpos *cp, int nacross, va_list ap)
+{
+    RECT r;
+    int group;
+    int i;
+    char *btext;
+
+    group = WS_GROUP;
+    i = 0;
+    btext = va_arg(ap, char *);
+    while (1) {
+       char *nextbtext;
+       int bid;
+       if (!btext)
+           break;
+       if (i == nacross) {
+           cp->ypos += r.bottom + GAPBETWEEN;
+           i = 0;
+       }
+       bid = va_arg(ap, int);
+       nextbtext = va_arg(ap, char *);
+       r.left = GAPBETWEEN + i * (cp->width + GAPBETWEEN) / nacross;
+       if (nextbtext)
+           r.right =
+               (i + 1) * (cp->width + GAPBETWEEN) / nacross - r.left;
+       else
+           r.right = cp->width - r.left;
+       r.top = cp->ypos;
+       r.bottom = RADIOHEIGHT;
+       doctl(cp, r, "BUTTON",
+             BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP |
+             group, 0, btext, bid);
+       group = 0;
+       i++;
+       btext = nextbtext;
+    }
+    cp->ypos += r.bottom + GAPBETWEEN;
 }
 
 /*
@@ -163,72 +221,77 @@ void multiedit(struct ctlpos *cp, ...) {
  * (you might want this not to equal the number of buttons if you
  * needed to line up some 2s and some 3s to look good in the same
  * panel).
+ * 
+ * There's a bit of a hack in here to ensure that if nacross
+ * exceeds the actual number of buttons, the rightmost button
+ * really does get all the space right to the edge of the line, so
+ * you can do things like
+ * 
+ * (*) Button1  (*) Button2  (*) ButtonWithReallyLongTitle
  */
-void radioline(struct ctlpos *cp,
-               char *text, int id, int nacross, ...) {
+void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...)
+{
     RECT r;
     va_list ap;
-    int group;
-    int i;
 
-    r.left = GAPBETWEEN; r.top = cp->ypos;
-    r.right = cp->width; r.bottom = STATICHEIGHT;
+    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);
-    group = WS_GROUP;
-    i = 0;
-    while (1) {
-        char *btext;
-        int bid;
-        btext = va_arg(ap, char *);
-        if (!btext)
-            break;
-        bid = va_arg(ap, int);
-        r.left = GAPBETWEEN + i * (cp->width+GAPBETWEEN)/nacross;
-        r.right = (i+1) * (cp->width+GAPBETWEEN)/nacross - r.left;
-        r.top = cp->ypos; r.bottom = RADIOHEIGHT;
-        doctl(cp, r, "BUTTON",
-              BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP | group,
-              0,
-              btext, bid);
-        group = 0;
-        i++;
-    }
+    radioline_common(cp, nacross, ap);
+    va_end(ap);
+}
+
+/*
+ * A set of radio buttons on the same line, without a static above
+ * them. Otherwise just like radioline.
+ */
+void bareradioline(struct ctlpos *cp, int nacross, ...)
+{
+    va_list ap;
+
+    va_start(ap, nacross);
+    radioline_common(cp, nacross, ap);
     va_end(ap);
-    cp->ypos += r.bottom + GAPBETWEEN;
 }
 
 /*
  * A set of radio buttons on multiple lines, with a static above
  * them.
  */
-void radiobig(struct ctlpos *cp, char *text, int id, ...) {
+void radiobig(struct ctlpos *cp, char *text, int id, ...)
+{
     RECT r;
     va_list ap;
     int group;
 
-    r.left = GAPBETWEEN; r.top = cp->ypos;
-    r.right = cp->width; r.bottom = STATICHEIGHT;
+    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;
     while (1) {
-        char *btext;
-        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;
+       char *btext;
+       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;
@@ -237,25 +300,31 @@ void radiobig(struct ctlpos *cp, char *text, int id, ...) {
 /*
  * A single standalone checkbox.
  */
-void checkbox(struct ctlpos *cp, char *text, int id) {
+void checkbox(struct ctlpos *cp, char *text, int id)
+{
     RECT r;
 
-    r.left = GAPBETWEEN; r.top = cp->ypos;
-    r.right = cp->width; r.bottom = CHECKBOXHEIGHT;
+    r.left = GAPBETWEEN;
+    r.top = cp->ypos;
+    r.right = cp->width;
+    r.bottom = CHECKBOXHEIGHT;
     cp->ypos += r.bottom + GAPBETWEEN;
     doctl(cp, r, "BUTTON",
-          BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0,
-          text, id);
+         BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0,
+         text, id);
 }
 
 /*
  * A single standalone static text control.
  */
-void statictext(struct ctlpos *cp, char *text, int id) {
+void statictext(struct ctlpos *cp, char *text, int id)
+{
     RECT r;
 
-    r.left = GAPBETWEEN; r.top = cp->ypos;
-    r.right = cp->width; r.bottom = STATICHEIGHT;
+    r.left = GAPBETWEEN;
+    r.top = cp->ypos;
+    r.right = cp->width;
+    r.bottom = STATICHEIGHT;
     cp->ypos += r.bottom + GAPBETWEEN;
     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
 }
@@ -264,26 +333,30 @@ void statictext(struct ctlpos *cp, char *text, int id) {
  * A button on the right hand side, with a static to its left.
  */
 void staticbtn(struct ctlpos *cp, char *stext, int sid,
-               char *btext, int bid) {
+              char *btext, int bid)
+{
     const int height = (PUSHBTNHEIGHT > STATICHEIGHT ?
-                        PUSHBTNHEIGHT : STATICHEIGHT);
+                       PUSHBTNHEIGHT : STATICHEIGHT);
     RECT r;
     int lwid, rwid, rpos;
 
     rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
-    lwid = rpos - 2*GAPBETWEEN;
+    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;
+    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-PUSHBTNHEIGHT)/2;
-    r.right = rwid; r.bottom = PUSHBTNHEIGHT;
+    r.left = rpos;
+    r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
+    r.right = rwid;
+    r.bottom = PUSHBTNHEIGHT;
     doctl(cp, r, "BUTTON",
-          WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
-          0,
-          btext, bid);
+         WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+         0, btext, bid);
 
     cp->ypos += height + GAPBETWEEN;
 }
@@ -292,38 +365,45 @@ void staticbtn(struct ctlpos *cp, char *stext, int sid,
  * An edit control on the right hand side, with a static to its left.
  */
 static void staticedit_internal(struct ctlpos *cp, char *stext,
-                                int sid, int eid, int percentedit,
-                                int style) {
+                               int sid, int eid, int percentedit,
+                               int style)
+{
     const int height = (EDITHEIGHT > STATICHEIGHT ?
-                        EDITHEIGHT : STATICHEIGHT);
+                       EDITHEIGHT : STATICHEIGHT);
     RECT r;
     int lwid, rwid, rpos;
 
-    rpos = GAPBETWEEN + (100-percentedit) * (cp->width + GAPBETWEEN) / 100;
-    lwid = rpos - 2*GAPBETWEEN;
+    rpos =
+       GAPBETWEEN + (100 - percentedit) * (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;
+    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 = EDITHEIGHT;
+    r.left = rpos;
+    r.top = cp->ypos + (height - EDITHEIGHT) / 2;
+    r.right = rwid;
+    r.bottom = EDITHEIGHT;
     doctl(cp, r, "EDIT",
-          WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | style,
-          WS_EX_CLIENTEDGE,
-          "", eid);
+         WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | style,
+         WS_EX_CLIENTEDGE, "", eid);
 
     cp->ypos += height + GAPBETWEEN;
 }
 
 void staticedit(struct ctlpos *cp, char *stext,
-                int sid, int eid, int percentedit) {
+               int sid, int eid, int percentedit)
+{
     staticedit_internal(cp, stext, sid, eid, percentedit, 0);
 }
 
 void staticpassedit(struct ctlpos *cp, char *stext,
-                    int sid, int eid, int percentedit) {
+                   int sid, int eid, int percentedit)
+{
     staticedit_internal(cp, stext, sid, eid, percentedit, ES_PASSWORD);
 }
 
@@ -331,59 +411,67 @@ void staticpassedit(struct ctlpos *cp, char *stext,
  * A big multiline edit control with a static labelling it.
  */
 void bigeditctrl(struct ctlpos *cp, char *stext,
-                 int sid, int eid, int lines) {
+                int sid, int eid, int lines)
+{
     RECT r;
 
-    r.left = GAPBETWEEN; r.top = cp->ypos;
-    r.right = cp->width; r.bottom = STATICHEIGHT;
+    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 = EDITHEIGHT + (lines-1) * STATICHEIGHT;
+    r.left = GAPBETWEEN;
+    r.top = cp->ypos;
+    r.right = cp->width;
+    r.bottom = EDITHEIGHT + (lines - 1) * STATICHEIGHT;
     cp->ypos += r.bottom + GAPBETWEEN;
     doctl(cp, r, "EDIT",
-          WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_MULTILINE,
-          WS_EX_CLIENTEDGE,
-          "", eid);
+         WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | ES_MULTILINE,
+         WS_EX_CLIENTEDGE, "", eid);
 }
 
 /*
  * A tab-control substitute when a real tab control is unavailable.
  */
-void ersatztab(struct ctlpos *cp, char *stext, int sid,
-               int lid, int s2id) {
+void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id)
+{
     const int height = (COMBOHEIGHT > STATICHEIGHT ?
-                        COMBOHEIGHT : STATICHEIGHT);
+                       COMBOHEIGHT : STATICHEIGHT);
     RECT r;
     int bigwid, lwid, rwid, rpos;
     static const int BIGGAP = 15;
     static const int MEDGAP = 3;
 
-    bigwid = cp->width + 2*GAPBETWEEN - 2*BIGGAP;
+    bigwid = cp->width + 2 * GAPBETWEEN - 2 * BIGGAP;
     cp->ypos += MEDGAP;
     rpos = BIGGAP + (bigwid + BIGGAP) / 2;
-    lwid = rpos - 2*BIGGAP;
+    lwid = rpos - 2 * BIGGAP;
     rwid = bigwid + BIGGAP - rpos;
 
-    r.left = BIGGAP; r.top = cp->ypos + (height-STATICHEIGHT)/2;
-    r.right = lwid; r.bottom = STATICHEIGHT;
+    r.left = BIGGAP;
+    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-COMBOHEIGHT)/2;
-    r.right = rwid; r.bottom = COMBOHEIGHT*10;
+    r.left = rpos;
+    r.top = cp->ypos + (height - COMBOHEIGHT) / 2;
+    r.right = rwid;
+    r.bottom = COMBOHEIGHT * 10;
     doctl(cp, r, "COMBOBOX",
-          WS_CHILD | WS_VISIBLE | WS_TABSTOP |
-          CBS_DROPDOWNLIST | CBS_HASSTRINGS,
-          WS_EX_CLIENTEDGE,
-          "", lid);
+         WS_CHILD | WS_VISIBLE | WS_TABSTOP |
+         CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
 
     cp->ypos += height + MEDGAP + GAPBETWEEN;
 
-    r.left = GAPBETWEEN; r.top = cp->ypos;
-    r.right = cp->width; r.bottom = 2;
+    r.left = GAPBETWEEN;
+    r.top = cp->ypos;
+    r.right = cp->width;
+    r.bottom = 2;
     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
-          0, "", s2id);
+         0, "", s2id);
 }
 
 /*
@@ -391,34 +479,39 @@ void ersatztab(struct ctlpos *cp, char *stext, int sid,
  * and a button on the right.
  */
 void editbutton(struct ctlpos *cp, char *stext, int sid,
-                int eid, char *btext, int bid) {
+               int eid, char *btext, int bid)
+{
     const int height = (EDITHEIGHT > PUSHBTNHEIGHT ?
-                        EDITHEIGHT : PUSHBTNHEIGHT);
+                       EDITHEIGHT : PUSHBTNHEIGHT);
     RECT r;
     int lwid, rwid, rpos;
 
-    r.left = GAPBETWEEN; r.top = cp->ypos;
-    r.right = cp->width; r.bottom = STATICHEIGHT;
+    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 + 3 * (cp->width + GAPBETWEEN) / 4;
-    lwid = rpos - 2*GAPBETWEEN;
+    lwid = rpos - 2 * GAPBETWEEN;
     rwid = cp->width + GAPBETWEEN - rpos;
 
-    r.left = GAPBETWEEN; r.top = cp->ypos + (height-EDITHEIGHT)/2;
-    r.right = lwid; r.bottom = EDITHEIGHT;
+    r.left = GAPBETWEEN;
+    r.top = cp->ypos + (height - EDITHEIGHT) / 2;
+    r.right = lwid;
+    r.bottom = EDITHEIGHT;
     doctl(cp, r, "EDIT",
-          WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
-          WS_EX_CLIENTEDGE,
-          "", eid);
+         WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
+         WS_EX_CLIENTEDGE, "", eid);
 
-    r.left = rpos; r.top = cp->ypos + (height-PUSHBTNHEIGHT)/2;
-    r.right = rwid; r.bottom = PUSHBTNHEIGHT;
+    r.left = rpos;
+    r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
+    r.right = rwid;
+    r.bottom = PUSHBTNHEIGHT;
     doctl(cp, r, "BUTTON",
-          WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
-          0,
-          btext, bid);
+         WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+         0, btext, bid);
 
     cp->ypos += height + GAPBETWEEN;
 }
@@ -430,7 +523,8 @@ void editbutton(struct ctlpos *cp, char *stext, int sid,
  * buttons.
  */
 void sesssaver(struct ctlpos *cp, char *text,
-               int staticid, int editid, int listid, ...) {
+              int staticid, int editid, int listid, ...)
+{
     RECT r;
     va_list ap;
     int lwid, rwid, rpos;
@@ -438,23 +532,26 @@ void sesssaver(struct ctlpos *cp, char *text,
     const int LISTDEFHEIGHT = 66;
 
     rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
-    lwid = rpos - 2*GAPBETWEEN;
+    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;
+    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);
 
     /* The edit control. */
-    r.left = GAPBETWEEN; r.top = cp->ypos;
-    r.right = lwid; r.bottom = EDITHEIGHT;
+    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);
+         WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
+         WS_EX_CLIENTEDGE, "", editid);
 
     /*
      * The buttons (we should hold off on the list box until we
@@ -463,31 +560,34 @@ void sesssaver(struct ctlpos *cp, char *text,
     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);
+       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);
     }
 
     /* 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;
+    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);
+         WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
+         LBS_NOTIFY | LBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", listid);
 }
 
 /*
@@ -496,66 +596,65 @@ void sesssaver(struct ctlpos *cp, char *text,
  * 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) {
+              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 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;
+    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;
+       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;
+    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);
+         WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | LBS_HASSTRINGS
+         | LBS_USETABSTOPS, WS_EX_CLIENTEDGE, "", listid);
 }
 
 /*
@@ -564,56 +663,58 @@ void envsetter(struct ctlpos *cp, char *stext, int sid,
  * 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) {
+              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 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 = 66;
 
     /* The static control. */
-    r.left = GAPBETWEEN; r.top = cp->ypos;
-    r.right = cp->width; r.bottom = STATICHEIGHT;
+    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;
+    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);
+         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);
-        }
+       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;
 }
@@ -624,7 +725,8 @@ void charclass(struct ctlpos *cp, char *stext, int sid, int listid,
  * two-part statics followed by a button.
  */
 void colouredit(struct ctlpos *cp, char *stext, int sid, int listid,
-                char *btext, int bid, ...) {
+               char *btext, int bid, ...)
+{
     RECT r;
     int y;
     va_list ap;
@@ -632,69 +734,400 @@ void colouredit(struct ctlpos *cp, char *stext, int sid, int listid,
     const int LISTHEIGHT = 66;
 
     /* The static control. */
-    r.left = GAPBETWEEN; r.top = cp->ypos;
-    r.right = cp->width; r.bottom = STATICHEIGHT;
+    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;
+    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;
+    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,
-          WS_EX_CLIENTEDGE,
-          "", listid);
+         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);
+       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;
+    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);
+         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;
+    }
+
+    for (i=0; i<3; i++) {
+        int left, wid;
+        xpos = (cp->width + GAPBETWEEN) * percent / 100;
+        left = xpos + GAPBETWEEN;
+        percent += percents[i];
+        xpos = (cp->width + GAPBETWEEN) * percent / 100;
+        wid = xpos - left;
+
+        switch (i) {
+          case 1:
+            /* The drag list box. */
+            r.left = left; r.right = wid;
+            r.top = cp->ypos; r.bottom = totalheight;
+            {
+                HWND ctl;
+                ctl = doctl(cp, r, "LISTBOX",
+                            WS_CHILD | WS_VISIBLE | WS_TABSTOP |
+                           WS_VSCROLL | LBS_HASSTRINGS,
+                            WS_EX_CLIENTEDGE,
+                            "", listid);
+               MakeDragList(ctl);
+            }
+            break;
+
+          case 2:
+            /* The "Up" and "Down" buttons. */
+           /* 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;
+            doctl(cp, r, "BUTTON",
+                  WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+                  0, "&Up", upbid);
+
+            r.left = left; r.right = wid;
+            r.top = cp->ypos + PUSHBTNHEIGHT + GAPBETWEEN;
+            r.bottom = PUSHBTNHEIGHT;
+            doctl(cp, r, "BUTTON",
+                  WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+                  0, "&Down", dnbid);
+
+            break;
+
+        }
+    }
+
+    cp->ypos += totalheight + GAPBETWEEN;
+
+}
+
+/*
+ * Helper function for prefslist: move item in list box.
+ */
+static void pl_moveitem(HWND hwnd, int listid, int src, int dst)
+{
+    int tlen, val;
+    char *txt;
+    /* Get the item's data. */
+    tlen = SendDlgItemMessage (hwnd, listid, LB_GETTEXTLEN, src, 0);
+    txt = smalloc(tlen+1);
+    SendDlgItemMessage (hwnd, listid, LB_GETTEXT, src, (LPARAM) txt);
+    val = SendDlgItemMessage (hwnd, listid, LB_GETITEMDATA, src, 0);
+    /* Deselect old location. */
+    SendDlgItemMessage (hwnd, listid, LB_SETSEL, FALSE, src);
+    /* Delete it at the old location. */
+    SendDlgItemMessage (hwnd, listid, LB_DELETESTRING, src, 0);
+    /* Insert it at new location. */
+    SendDlgItemMessage (hwnd, listid, LB_INSERTSTRING, dst,
+                       (LPARAM) txt);
+    SendDlgItemMessage (hwnd, listid, LB_SETITEMDATA, dst,
+                       (LPARAM) val);
+    /* Set selection. */
+    SendDlgItemMessage (hwnd, listid, LB_SETCURSEL, dst, 0);
+    sfree (txt);
+}
+
+int pl_itemfrompt(HWND hwnd, POINT cursor, BOOL scroll)
+{
+    int ret;
+    POINT uppoint, downpoint;
+    int updist, downdist, upitem, downitem, i;
+
+    /*
+     * Ghastly hackery to try to figure out not which
+     * _item_, but which _gap between items_, the user
+     * is pointing at. We do this by first working out
+     * which list item is under the cursor, and then
+     * working out how far the cursor would have to
+     * move up or down before the answer was different.
+     * Then we put the insertion point _above_ the
+     * current item if the upper edge is closer than
+     * the lower edge, or _below_ it if vice versa.
+     */
+    ret = LBItemFromPt(hwnd, cursor, scroll);
+    debug(("pl_itemfrompt: initial is %d\n", ret));
+    if (ret == -1)
+       return ret;
+    ret = LBItemFromPt(hwnd, cursor, FALSE);
+    debug(("pl_itemfrompt: secondary is %d\n", ret));
+    updist = downdist = 0;
+    for (i = 1; i < 4096 && (!updist || !downdist); i++) {
+       uppoint = downpoint = cursor;
+       uppoint.y -= i;
+       downpoint.y += i;
+       upitem = LBItemFromPt(hwnd, uppoint, FALSE);
+       downitem = LBItemFromPt(hwnd, downpoint, FALSE);
+       if (!updist && upitem != ret)
+           updist = i;
+       if (!downdist && downitem != ret)
+           downdist = i;
+    }
+    if (downdist < updist)
+       ret++;
+    return ret;
+}
+
+/*
+ * Handler for prefslist above.
+ */
+int handle_prefslist(struct prefslist *hdl,
+                     int *array, int maxmemb,
+                     int is_dlmsg, HWND hwnd,
+                    WPARAM wParam, LPARAM lParam)
+{
+    int i;
+    int ret;
+
+    if (is_dlmsg) {
+
+        if (wParam == hdl->listid) {
+            DRAGLISTINFO *dlm = (DRAGLISTINFO *)lParam;
+            int dest;
+            switch (dlm->uNotification) {
+              case DL_BEGINDRAG:
+               hdl->dummyitem =
+                   SendDlgItemMessage(hwnd, hdl->listid,
+                                      LB_ADDSTRING, 0, (LPARAM) "");
+
+                hdl->srcitem = LBItemFromPt(dlm->hWnd, dlm->ptCursor, TRUE);
+               hdl->dragging = 0;
+               /* XXX hack Q183115 */
+               SetWindowLong(hwnd, DWL_MSGRESULT, TRUE);
+                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;
+              case DL_DRAGGING:
+               hdl->dragging = 1;
+               dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, TRUE);
+               if (dest > hdl->dummyitem) dest = hdl->dummyitem;
+               DrawInsert (hwnd, dlm->hWnd, dest);
+               if (dest >= 0)
+                   SetWindowLong(hwnd, DWL_MSGRESULT, DL_MOVECURSOR);
+               else
+                   SetWindowLong(hwnd, DWL_MSGRESULT, DL_STOPCURSOR);
+                ret = 1; break;
+              case DL_DROPPED:
+               ret = 1;
+               if (!hdl->dragging) break;
+               dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, TRUE);
+               if (dest > hdl->dummyitem) dest = hdl->dummyitem;
+               DrawInsert (hwnd, dlm->hWnd, -1);
+               SendDlgItemMessage(hwnd, hdl->listid,
+                                  LB_DELETESTRING, hdl->dummyitem, 0);
+               hdl->dragging = 0;
+               if (dest >= 0) {
+                   /* Correct for "missing" item. This means you can't drag
+                    * an item to the end, but that seems to be the way this
+                    * control is used. */
+                   if (dest > hdl->srcitem) dest--;
+                   pl_moveitem(hwnd, hdl->listid, hdl->srcitem, dest);
+               }
+                ret = 1; break;
+            }
+        }
+
+    } else {
+
+        ret = 0;
+        if (((LOWORD(wParam) == hdl->upbid) ||
+             (LOWORD(wParam) == hdl->dnbid)) &&
+            ((HIWORD(wParam) == BN_CLICKED) ||
+             (HIWORD(wParam) == BN_DOUBLECLICKED))) {
+            /* Move an item up or down the list. */
+            /* Get the current selection, if any. */
+            int selection = SendDlgItemMessage (hwnd, hdl->listid, LB_GETCURSEL, 0, 0);
+            if (selection == LB_ERR) {
+                MessageBeep(0);
+            } else {
+                int nitems;
+                /* Get the total number of items. */
+                nitems = SendDlgItemMessage (hwnd, hdl->listid, LB_GETCOUNT, 0, 0);
+                /* Should we do anything? */
+               if (LOWORD(wParam) == hdl->upbid && (selection > 0))
+                   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);
+            }
+
+        }
+
+    }
+
+    /* 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;
+
+}
+
+/*
  * A progress bar (from Common Controls). We like our progress bars
  * to be smooth and unbroken, without those ugly divisions; some
  * older compilers may not support that, but that's life.
  */
-void progressbar(struct ctlpos *cp, int id) {
+void progressbar(struct ctlpos *cp, int id)
+{
     RECT r;
 
-    r.left = GAPBETWEEN; r.top = cp->ypos;
-    r.right = cp->width; r.bottom = PROGBARHEIGHT;
+    r.left = GAPBETWEEN;
+    r.top = cp->ypos;
+    r.right = cp->width;
+    r.bottom = PROGBARHEIGHT;
     cp->ypos += r.bottom + GAPBETWEEN;
 
-    doctl(cp, r, PROGRESS_CLASS,
-          WS_CHILD | WS_VISIBLE
+    doctl(cp, r, PROGRESS_CLASS, WS_CHILD | WS_VISIBLE
 #ifdef PBS_SMOOTH
-          | PBS_SMOOTH
+         | PBS_SMOOTH
 #endif
-          , WS_EX_CLIENTEDGE, "", id);
+         , WS_EX_CLIENTEDGE, "", id);
+}
+
+/*
+ * 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.
+ */
+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)
+{
+    RECT 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);
+
+    /* 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 = 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) {
+               doctl(cp, r, "BUTTON",
+                     WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
+                     0, btext, bid);
+           }
+       }
+       cp->ypos += height + GAPWITHIN;
+    }
 }