Jacob's patch for a drag-list to select SSH ciphers. Heavily hacked
[u/mdw/putty] / winctrls.c
index f87d265..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
@@ -39,7 +42,7 @@ void ctlposinit(struct ctlpos *cp, HWND hwnd,
     cp->width -= leftborder + rightborder;
 }
 
-void doctl(struct ctlpos *cp, RECT r,
+HWND doctl(struct ctlpos *cp, RECT r,
           char *wclass, int wstyle, int exstyle, char *wtext, int wid)
 {
     HWND ctl;
@@ -56,6 +59,7 @@ void doctl(struct ctlpos *cp, RECT r,
                         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;
 }
 
 /*
@@ -145,38 +149,40 @@ void multiedit(struct ctlpos *cp, ...)
              WS_EX_CLIENTEDGE, "", editid);
     }
     va_end(ap);
-    cp->ypos += 8 + GAPWITHIN + 12 + GAPBETWEEN;
+    cp->ypos += STATICHEIGHT + GAPWITHIN + EDITHEIGHT + GAPBETWEEN;
 }
 
 /*
- * A set of radio buttons on the same line, with a static above
- * them. `nacross' dictates how many parts the line is divided into
- * (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
+ * A static line, followed by a full-width drop-down list (ie a
+ * non-editing combo box).
  */
-void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...)
+void dropdownlist(struct ctlpos *cp, char *text, int staticid, int listid)
 {
     RECT r;
     va_list ap;
-    int group;
-    int i;
-    char *btext;
 
     r.left = GAPBETWEEN;
-    r.top = cp->ypos;
     r.right = cp->width;
+
+    r.top = cp->ypos;
     r.bottom = STATICHEIGHT;
-    cp->ypos += r.bottom + GAPWITHIN;
-    doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
-    va_start(ap, nacross);
+    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 *);
@@ -206,11 +212,53 @@ void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...)
        i++;
        btext = nextbtext;
     }
-    va_end(ap);
     cp->ypos += r.bottom + GAPBETWEEN;
 }
 
 /*
+ * A set of radio buttons on the same line, with a static above
+ * them. `nacross' dictates how many parts the line is divided into
+ * (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, ...)
+{
+    RECT r;
+    va_list ap;
+
+    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);
+    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);
+}
+
+/*
  * A set of radio buttons on multiple lines, with a static above
  * them.
  */
@@ -743,6 +791,257 @@ void colouredit(struct ctlpos *cp, char *stext, int sid, int listid,
 }
 
 /*
+ * 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.
@@ -763,3 +1062,72 @@ void progressbar(struct ctlpos *cp, int id)
 #endif
          , 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;
+    }
+}