]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - winctrls.c
More upgrades to psftp: it now supports mv, chmod, reget and reput.
[PuTTY.git] / winctrls.c
1 /*
2  * winctrls.c: routines to self-manage the controls in a dialog
3  * box.
4  */
5
6 #include <windows.h>
7 #include <commctrl.h>
8
9 #include "winstuff.h"
10 #include "puttymem.h"
11
12 #include "putty.h"
13
14 #define GAPBETWEEN 3
15 #define GAPWITHIN 1
16 #define GAPXBOX 7
17 #define GAPYBOX 4
18 #define DLGWIDTH 168
19 #define STATICHEIGHT 8
20 #define CHECKBOXHEIGHT 8
21 #define RADIOHEIGHT 8
22 #define EDITHEIGHT 12
23 #define COMBOHEIGHT 12
24 #define PUSHBTNHEIGHT 14
25 #define PROGBARHEIGHT 14
26
27 void ctlposinit(struct ctlpos *cp, HWND hwnd,
28                 int leftborder, int rightborder, int topborder)
29 {
30     RECT r, r2;
31     cp->hwnd = hwnd;
32     cp->font = SendMessage(hwnd, WM_GETFONT, 0, 0);
33     cp->ypos = topborder;
34     GetClientRect(hwnd, &r);
35     r2.left = r2.top = 0;
36     r2.right = 4;
37     r2.bottom = 8;
38     MapDialogRect(hwnd, &r2);
39     cp->dlu4inpix = r2.right;
40     cp->width = (r.right * 4) / (r2.right) - 2 * GAPBETWEEN;
41     cp->xoff = leftborder;
42     cp->width -= leftborder + rightborder;
43 }
44
45 HWND doctl(struct ctlpos *cp, RECT r,
46            char *wclass, int wstyle, int exstyle, char *wtext, int wid)
47 {
48     HWND ctl;
49     /*
50      * Note nonstandard use of RECT. This is deliberate: by
51      * transforming the width and height directly we arrange to
52      * have all supposedly same-sized controls really same-sized.
53      */
54
55     r.left += cp->xoff;
56     MapDialogRect(cp->hwnd, &r);
57
58     ctl = CreateWindowEx(exstyle, wclass, wtext, wstyle,
59                          r.left, r.top, r.right, r.bottom,
60                          cp->hwnd, (HMENU) wid, hinst, NULL);
61     SendMessage(ctl, WM_SETFONT, cp->font, MAKELPARAM(TRUE, 0));
62     return ctl;
63 }
64
65 /*
66  * A title bar across the top of a sub-dialog.
67  */
68 void bartitle(struct ctlpos *cp, char *name, int id)
69 {
70     RECT r;
71
72     r.left = GAPBETWEEN;
73     r.right = cp->width;
74     r.top = cp->ypos;
75     r.bottom = STATICHEIGHT;
76     cp->ypos += r.bottom + GAPBETWEEN;
77     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, name, id);
78 }
79
80 /*
81  * Begin a grouping box, with or without a group title.
82  */
83 void beginbox(struct ctlpos *cp, char *name, int idbox)
84 {
85     cp->boxystart = cp->ypos;
86     if (!name)
87         cp->boxystart -= STATICHEIGHT / 2;
88     if (name)
89         cp->ypos += STATICHEIGHT;
90     cp->ypos += GAPYBOX;
91     cp->width -= 2 * GAPXBOX;
92     cp->xoff += GAPXBOX;
93     cp->boxid = idbox;
94     cp->boxtext = name;
95 }
96
97 /*
98  * End a grouping box.
99  */
100 void endbox(struct ctlpos *cp)
101 {
102     RECT r;
103     cp->xoff -= GAPXBOX;
104     cp->width += 2 * GAPXBOX;
105     cp->ypos += GAPYBOX - GAPBETWEEN;
106     r.left = GAPBETWEEN;
107     r.right = cp->width;
108     r.top = cp->boxystart;
109     r.bottom = cp->ypos - cp->boxystart;
110     doctl(cp, r, "BUTTON", BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 0,
111           cp->boxtext ? cp->boxtext : "", cp->boxid);
112     cp->ypos += GAPYBOX;
113 }
114
115 /*
116  * Some edit boxes. Each one has a static above it. The percentages
117  * of the horizontal space are provided.
118  */
119 void multiedit(struct ctlpos *cp, ...)
120 {
121     RECT r;
122     va_list ap;
123     int percent, xpos;
124
125     percent = xpos = 0;
126     va_start(ap, cp);
127     while (1) {
128         char *text;
129         int staticid, editid, pcwidth;
130         text = va_arg(ap, char *);
131         if (!text)
132             break;
133         staticid = va_arg(ap, int);
134         editid = va_arg(ap, int);
135         pcwidth = va_arg(ap, int);
136
137         r.left = xpos + GAPBETWEEN;
138         percent += pcwidth;
139         xpos = (cp->width + GAPBETWEEN) * percent / 100;
140         r.right = xpos - r.left;
141
142         r.top = cp->ypos;
143         r.bottom = STATICHEIGHT;
144         doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, staticid);
145         r.top = cp->ypos + 8 + GAPWITHIN;
146         r.bottom = EDITHEIGHT;
147         doctl(cp, r, "EDIT",
148               WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
149               WS_EX_CLIENTEDGE, "", editid);
150     }
151     va_end(ap);
152     cp->ypos += STATICHEIGHT + GAPWITHIN + EDITHEIGHT + GAPBETWEEN;
153 }
154
155 /*
156  * A static line, followed by a full-width drop-down list (ie a
157  * non-editing combo box).
158  */
159 void dropdownlist(struct ctlpos *cp, char *text, int staticid, int listid)
160 {
161     RECT r;
162     va_list ap;
163
164     r.left = GAPBETWEEN;
165     r.right = cp->width;
166
167     r.top = cp->ypos;
168     r.bottom = STATICHEIGHT;
169     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, staticid);
170     r.top = cp->ypos + 8 + GAPWITHIN;
171     r.bottom = COMBOHEIGHT * 10;
172     doctl(cp, r, "COMBOBOX",
173           WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
174           CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", listid);
175
176     cp->ypos += STATICHEIGHT + GAPWITHIN + COMBOHEIGHT + GAPBETWEEN;
177 }
178
179 static void radioline_common(struct ctlpos *cp, int nacross, va_list ap)
180 {
181     RECT r;
182     int group;
183     int i;
184     char *btext;
185
186     group = WS_GROUP;
187     i = 0;
188     btext = va_arg(ap, char *);
189     while (1) {
190         char *nextbtext;
191         int bid;
192         if (!btext)
193             break;
194         if (i == nacross) {
195             cp->ypos += r.bottom + GAPBETWEEN;
196             i = 0;
197         }
198         bid = va_arg(ap, int);
199         nextbtext = va_arg(ap, char *);
200         r.left = GAPBETWEEN + i * (cp->width + GAPBETWEEN) / nacross;
201         if (nextbtext)
202             r.right =
203                 (i + 1) * (cp->width + GAPBETWEEN) / nacross - r.left;
204         else
205             r.right = cp->width - r.left;
206         r.top = cp->ypos;
207         r.bottom = RADIOHEIGHT;
208         doctl(cp, r, "BUTTON",
209               BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP |
210               group, 0, btext, bid);
211         group = 0;
212         i++;
213         btext = nextbtext;
214     }
215     cp->ypos += r.bottom + GAPBETWEEN;
216 }
217
218 /*
219  * A set of radio buttons on the same line, with a static above
220  * them. `nacross' dictates how many parts the line is divided into
221  * (you might want this not to equal the number of buttons if you
222  * needed to line up some 2s and some 3s to look good in the same
223  * panel).
224  * 
225  * There's a bit of a hack in here to ensure that if nacross
226  * exceeds the actual number of buttons, the rightmost button
227  * really does get all the space right to the edge of the line, so
228  * you can do things like
229  * 
230  * (*) Button1  (*) Button2  (*) ButtonWithReallyLongTitle
231  */
232 void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...)
233 {
234     RECT r;
235     va_list ap;
236
237     r.left = GAPBETWEEN;
238     r.top = cp->ypos;
239     r.right = cp->width;
240     r.bottom = STATICHEIGHT;
241     cp->ypos += r.bottom + GAPWITHIN;
242     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
243     va_start(ap, nacross);
244     radioline_common(cp, nacross, ap);
245     va_end(ap);
246 }
247
248 /*
249  * A set of radio buttons on the same line, without a static above
250  * them. Otherwise just like radioline.
251  */
252 void bareradioline(struct ctlpos *cp, int nacross, ...)
253 {
254     va_list ap;
255
256     va_start(ap, nacross);
257     radioline_common(cp, nacross, ap);
258     va_end(ap);
259 }
260
261 /*
262  * A set of radio buttons on multiple lines, with a static above
263  * them.
264  */
265 void radiobig(struct ctlpos *cp, char *text, int id, ...)
266 {
267     RECT r;
268     va_list ap;
269     int group;
270
271     r.left = GAPBETWEEN;
272     r.top = cp->ypos;
273     r.right = cp->width;
274     r.bottom = STATICHEIGHT;
275     cp->ypos += r.bottom + GAPWITHIN;
276     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
277     va_start(ap, id);
278     group = WS_GROUP;
279     while (1) {
280         char *btext;
281         int bid;
282         btext = va_arg(ap, char *);
283         if (!btext)
284             break;
285         bid = va_arg(ap, int);
286         r.left = GAPBETWEEN;
287         r.top = cp->ypos;
288         r.right = cp->width;
289         r.bottom = STATICHEIGHT;
290         cp->ypos += r.bottom + GAPWITHIN;
291         doctl(cp, r, "BUTTON",
292               BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP |
293               group, 0, btext, bid);
294         group = 0;
295     }
296     va_end(ap);
297     cp->ypos += GAPBETWEEN - GAPWITHIN;
298 }
299
300 /*
301  * A single standalone checkbox.
302  */
303 void checkbox(struct ctlpos *cp, char *text, int id)
304 {
305     RECT r;
306
307     r.left = GAPBETWEEN;
308     r.top = cp->ypos;
309     r.right = cp->width;
310     r.bottom = CHECKBOXHEIGHT;
311     cp->ypos += r.bottom + GAPBETWEEN;
312     doctl(cp, r, "BUTTON",
313           BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0,
314           text, id);
315 }
316
317 /*
318  * A single standalone static text control.
319  */
320 void statictext(struct ctlpos *cp, char *text, int id)
321 {
322     RECT r;
323
324     r.left = GAPBETWEEN;
325     r.top = cp->ypos;
326     r.right = cp->width;
327     r.bottom = STATICHEIGHT;
328     cp->ypos += r.bottom + GAPBETWEEN;
329     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
330 }
331
332 /*
333  * A button on the right hand side, with a static to its left.
334  */
335 void staticbtn(struct ctlpos *cp, char *stext, int sid,
336                char *btext, int bid)
337 {
338     const int height = (PUSHBTNHEIGHT > STATICHEIGHT ?
339                         PUSHBTNHEIGHT : STATICHEIGHT);
340     RECT r;
341     int lwid, rwid, rpos;
342
343     rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
344     lwid = rpos - 2 * GAPBETWEEN;
345     rwid = cp->width + GAPBETWEEN - rpos;
346
347     r.left = GAPBETWEEN;
348     r.top = cp->ypos + (height - STATICHEIGHT) / 2;
349     r.right = lwid;
350     r.bottom = STATICHEIGHT;
351     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
352
353     r.left = rpos;
354     r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
355     r.right = rwid;
356     r.bottom = PUSHBTNHEIGHT;
357     doctl(cp, r, "BUTTON",
358           WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
359           0, btext, bid);
360
361     cp->ypos += height + GAPBETWEEN;
362 }
363
364 /*
365  * An edit control on the right hand side, with a static to its left.
366  */
367 static void staticedit_internal(struct ctlpos *cp, char *stext,
368                                 int sid, int eid, int percentedit,
369                                 int style)
370 {
371     const int height = (EDITHEIGHT > STATICHEIGHT ?
372                         EDITHEIGHT : STATICHEIGHT);
373     RECT r;
374     int lwid, rwid, rpos;
375
376     rpos =
377         GAPBETWEEN + (100 - percentedit) * (cp->width + GAPBETWEEN) / 100;
378     lwid = rpos - 2 * GAPBETWEEN;
379     rwid = cp->width + GAPBETWEEN - rpos;
380
381     r.left = GAPBETWEEN;
382     r.top = cp->ypos + (height - STATICHEIGHT) / 2;
383     r.right = lwid;
384     r.bottom = STATICHEIGHT;
385     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
386
387     r.left = rpos;
388     r.top = cp->ypos + (height - EDITHEIGHT) / 2;
389     r.right = rwid;
390     r.bottom = EDITHEIGHT;
391     doctl(cp, r, "EDIT",
392           WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | style,
393           WS_EX_CLIENTEDGE, "", eid);
394
395     cp->ypos += height + GAPBETWEEN;
396 }
397
398 void staticedit(struct ctlpos *cp, char *stext,
399                 int sid, int eid, int percentedit)
400 {
401     staticedit_internal(cp, stext, sid, eid, percentedit, 0);
402 }
403
404 void staticpassedit(struct ctlpos *cp, char *stext,
405                     int sid, int eid, int percentedit)
406 {
407     staticedit_internal(cp, stext, sid, eid, percentedit, ES_PASSWORD);
408 }
409
410 /*
411  * A big multiline edit control with a static labelling it.
412  */
413 void bigeditctrl(struct ctlpos *cp, char *stext,
414                  int sid, int eid, int lines)
415 {
416     RECT r;
417
418     r.left = GAPBETWEEN;
419     r.top = cp->ypos;
420     r.right = cp->width;
421     r.bottom = STATICHEIGHT;
422     cp->ypos += r.bottom + GAPWITHIN;
423     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
424
425     r.left = GAPBETWEEN;
426     r.top = cp->ypos;
427     r.right = cp->width;
428     r.bottom = EDITHEIGHT + (lines - 1) * STATICHEIGHT;
429     cp->ypos += r.bottom + GAPBETWEEN;
430     doctl(cp, r, "EDIT",
431           WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | ES_MULTILINE,
432           WS_EX_CLIENTEDGE, "", eid);
433 }
434
435 /*
436  * A tab-control substitute when a real tab control is unavailable.
437  */
438 void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id)
439 {
440     const int height = (COMBOHEIGHT > STATICHEIGHT ?
441                         COMBOHEIGHT : STATICHEIGHT);
442     RECT r;
443     int bigwid, lwid, rwid, rpos;
444     static const int BIGGAP = 15;
445     static const int MEDGAP = 3;
446
447     bigwid = cp->width + 2 * GAPBETWEEN - 2 * BIGGAP;
448     cp->ypos += MEDGAP;
449     rpos = BIGGAP + (bigwid + BIGGAP) / 2;
450     lwid = rpos - 2 * BIGGAP;
451     rwid = bigwid + BIGGAP - rpos;
452
453     r.left = BIGGAP;
454     r.top = cp->ypos + (height - STATICHEIGHT) / 2;
455     r.right = lwid;
456     r.bottom = STATICHEIGHT;
457     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
458
459     r.left = rpos;
460     r.top = cp->ypos + (height - COMBOHEIGHT) / 2;
461     r.right = rwid;
462     r.bottom = COMBOHEIGHT * 10;
463     doctl(cp, r, "COMBOBOX",
464           WS_CHILD | WS_VISIBLE | WS_TABSTOP |
465           CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
466
467     cp->ypos += height + MEDGAP + GAPBETWEEN;
468
469     r.left = GAPBETWEEN;
470     r.top = cp->ypos;
471     r.right = cp->width;
472     r.bottom = 2;
473     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
474           0, "", s2id);
475 }
476
477 /*
478  * A static line, followed by an edit control on the left hand side
479  * and a button on the right.
480  */
481 void editbutton(struct ctlpos *cp, char *stext, int sid,
482                 int eid, char *btext, int bid)
483 {
484     const int height = (EDITHEIGHT > PUSHBTNHEIGHT ?
485                         EDITHEIGHT : PUSHBTNHEIGHT);
486     RECT r;
487     int lwid, rwid, rpos;
488
489     r.left = GAPBETWEEN;
490     r.top = cp->ypos;
491     r.right = cp->width;
492     r.bottom = STATICHEIGHT;
493     cp->ypos += r.bottom + GAPWITHIN;
494     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
495
496     rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
497     lwid = rpos - 2 * GAPBETWEEN;
498     rwid = cp->width + GAPBETWEEN - rpos;
499
500     r.left = GAPBETWEEN;
501     r.top = cp->ypos + (height - EDITHEIGHT) / 2;
502     r.right = lwid;
503     r.bottom = EDITHEIGHT;
504     doctl(cp, r, "EDIT",
505           WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
506           WS_EX_CLIENTEDGE, "", eid);
507
508     r.left = rpos;
509     r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
510     r.right = rwid;
511     r.bottom = PUSHBTNHEIGHT;
512     doctl(cp, r, "BUTTON",
513           WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
514           0, btext, bid);
515
516     cp->ypos += height + GAPBETWEEN;
517 }
518
519 /*
520  * Special control which was hard to describe generically: the
521  * session-saver assembly. A static; below that an edit box; below
522  * that a list box. To the right of the list box, a column of
523  * buttons.
524  */
525 void sesssaver(struct ctlpos *cp, char *text,
526                int staticid, int editid, int listid, ...)
527 {
528     RECT r;
529     va_list ap;
530     int lwid, rwid, rpos;
531     int y;
532     const int LISTDEFHEIGHT = 66;
533
534     rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
535     lwid = rpos - 2 * GAPBETWEEN;
536     rwid = cp->width + GAPBETWEEN - rpos;
537
538     /* The static control. */
539     r.left = GAPBETWEEN;
540     r.top = cp->ypos;
541     r.right = lwid;
542     r.bottom = STATICHEIGHT;
543     cp->ypos += r.bottom + GAPWITHIN;
544     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, staticid);
545
546     /* The edit control. */
547     r.left = GAPBETWEEN;
548     r.top = cp->ypos;
549     r.right = lwid;
550     r.bottom = EDITHEIGHT;
551     cp->ypos += r.bottom + GAPWITHIN;
552     doctl(cp, r, "EDIT",
553           WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
554           WS_EX_CLIENTEDGE, "", editid);
555
556     /*
557      * The buttons (we should hold off on the list box until we
558      * know how big the buttons are).
559      */
560     va_start(ap, listid);
561     y = cp->ypos;
562     while (1) {
563         char *btext = va_arg(ap, char *);
564         int bid;
565         if (!btext)
566             break;
567         bid = va_arg(ap, int);
568         r.left = rpos;
569         r.top = y;
570         r.right = rwid;
571         r.bottom = PUSHBTNHEIGHT;
572         y += r.bottom + GAPWITHIN;
573         doctl(cp, r, "BUTTON",
574               WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
575               0, btext, bid);
576     }
577
578     /* Compute list box height. LISTDEFHEIGHT, or height of buttons. */
579     y -= cp->ypos;
580     y -= GAPWITHIN;
581     if (y < LISTDEFHEIGHT)
582         y = LISTDEFHEIGHT;
583     r.left = GAPBETWEEN;
584     r.top = cp->ypos;
585     r.right = lwid;
586     r.bottom = y;
587     cp->ypos += y + GAPBETWEEN;
588     doctl(cp, r, "LISTBOX",
589           WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
590           LBS_NOTIFY | LBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", listid);
591 }
592
593 /*
594  * Another special control: the environment-variable setter. A
595  * static line first; then a pair of edit boxes with associated
596  * statics, and two buttons; then a list box.
597  */
598 void envsetter(struct ctlpos *cp, char *stext, int sid,
599                char *e1stext, int e1sid, int e1id,
600                char *e2stext, int e2sid, int e2id,
601                int listid, char *b1text, int b1id, char *b2text, int b2id)
602 {
603     RECT r;
604     const int height = (STATICHEIGHT > EDITHEIGHT
605                         && STATICHEIGHT >
606                         PUSHBTNHEIGHT ? STATICHEIGHT : EDITHEIGHT >
607                         PUSHBTNHEIGHT ? EDITHEIGHT : PUSHBTNHEIGHT);
608     const static int percents[] = { 20, 35, 10, 25 };
609     int i, j, xpos, percent;
610     const int LISTHEIGHT = 42;
611
612     /* The static control. */
613     r.left = GAPBETWEEN;
614     r.top = cp->ypos;
615     r.right = cp->width;
616     r.bottom = STATICHEIGHT;
617     cp->ypos += r.bottom + GAPWITHIN;
618     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
619
620     /* The statics+edits+buttons. */
621     for (j = 0; j < 2; j++) {
622         percent = 10;
623         for (i = 0; i < 4; i++) {
624             xpos = (cp->width + GAPBETWEEN) * percent / 100;
625             r.left = xpos + GAPBETWEEN;
626             percent += percents[i];
627             xpos = (cp->width + GAPBETWEEN) * percent / 100;
628             r.right = xpos - r.left;
629             r.top = cp->ypos;
630             r.bottom = (i == 0 ? STATICHEIGHT :
631                         i == 1 ? EDITHEIGHT : PUSHBTNHEIGHT);
632             r.top += (height - r.bottom) / 2;
633             if (i == 0) {
634                 doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0,
635                       j == 0 ? e1stext : e2stext, j == 0 ? e1sid : e2sid);
636             } else if (i == 1) {
637                 doctl(cp, r, "EDIT",
638                       WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
639                       WS_EX_CLIENTEDGE, "", j == 0 ? e1id : e2id);
640             } else if (i == 3) {
641                 doctl(cp, r, "BUTTON",
642                       WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
643                       0, j == 0 ? b1text : b2text, j == 0 ? b1id : b2id);
644             }
645         }
646         cp->ypos += height + GAPWITHIN;
647     }
648
649     /* The list box. */
650     r.left = GAPBETWEEN;
651     r.top = cp->ypos;
652     r.right = cp->width;
653     r.bottom = LISTHEIGHT;
654     cp->ypos += r.bottom + GAPBETWEEN;
655     doctl(cp, r, "LISTBOX",
656           WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | LBS_HASSTRINGS
657           | LBS_USETABSTOPS, WS_EX_CLIENTEDGE, "", listid);
658 }
659
660 /*
661  * Yet another special control: the character-class setter. A
662  * static, then a list, then a line containing a
663  * button-and-static-and-edit. 
664  */
665 void charclass(struct ctlpos *cp, char *stext, int sid, int listid,
666                char *btext, int bid, int eid, char *s2text, int s2id)
667 {
668     RECT r;
669     const int height = (STATICHEIGHT > EDITHEIGHT
670                         && STATICHEIGHT >
671                         PUSHBTNHEIGHT ? STATICHEIGHT : EDITHEIGHT >
672                         PUSHBTNHEIGHT ? EDITHEIGHT : PUSHBTNHEIGHT);
673     const static int percents[] = { 30, 40, 30 };
674     int i, xpos, percent;
675     const int LISTHEIGHT = 66;
676
677     /* The static control. */
678     r.left = GAPBETWEEN;
679     r.top = cp->ypos;
680     r.right = cp->width;
681     r.bottom = STATICHEIGHT;
682     cp->ypos += r.bottom + GAPWITHIN;
683     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
684
685     /* The list box. */
686     r.left = GAPBETWEEN;
687     r.top = cp->ypos;
688     r.right = cp->width;
689     r.bottom = LISTHEIGHT;
690     cp->ypos += r.bottom + GAPWITHIN;
691     doctl(cp, r, "LISTBOX",
692           WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | LBS_HASSTRINGS
693           | LBS_USETABSTOPS, WS_EX_CLIENTEDGE, "", listid);
694
695     /* The button+static+edit. */
696     percent = xpos = 0;
697     for (i = 0; i < 3; i++) {
698         r.left = xpos + GAPBETWEEN;
699         percent += percents[i];
700         xpos = (cp->width + GAPBETWEEN) * percent / 100;
701         r.right = xpos - r.left;
702         r.top = cp->ypos;
703         r.bottom = (i == 0 ? PUSHBTNHEIGHT :
704                     i == 1 ? STATICHEIGHT : EDITHEIGHT);
705         r.top += (height - r.bottom) / 2;
706         if (i == 0) {
707             doctl(cp, r, "BUTTON",
708                   WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
709                   0, btext, bid);
710         } else if (i == 1) {
711             doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_CENTER,
712                   0, s2text, s2id);
713         } else if (i == 2) {
714             doctl(cp, r, "EDIT",
715                   WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
716                   WS_EX_CLIENTEDGE, "", eid);
717         }
718     }
719     cp->ypos += height + GAPBETWEEN;
720 }
721
722 /*
723  * A special control (horrors!). The colour editor. A static line;
724  * then on the left, a list box, and on the right, a sequence of
725  * two-part statics followed by a button.
726  */
727 void colouredit(struct ctlpos *cp, char *stext, int sid, int listid,
728                 char *btext, int bid, ...)
729 {
730     RECT r;
731     int y;
732     va_list ap;
733     int lwid, rwid, rpos;
734     const int LISTHEIGHT = 66;
735
736     /* The static control. */
737     r.left = GAPBETWEEN;
738     r.top = cp->ypos;
739     r.right = cp->width;
740     r.bottom = STATICHEIGHT;
741     cp->ypos += r.bottom + GAPWITHIN;
742     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
743
744     rpos = GAPBETWEEN + 2 * (cp->width + GAPBETWEEN) / 3;
745     lwid = rpos - 2 * GAPBETWEEN;
746     rwid = cp->width + GAPBETWEEN - rpos;
747
748     /* The list box. */
749     r.left = GAPBETWEEN;
750     r.top = cp->ypos;
751     r.right = lwid;
752     r.bottom = LISTHEIGHT;
753     doctl(cp, r, "LISTBOX",
754           WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | LBS_HASSTRINGS
755           | LBS_USETABSTOPS | LBS_NOTIFY, WS_EX_CLIENTEDGE, "", listid);
756
757     /* The statics. */
758     y = cp->ypos;
759     va_start(ap, bid);
760     while (1) {
761         char *ltext;
762         int lid, rid;
763         ltext = va_arg(ap, char *);
764         if (!ltext)
765             break;
766         lid = va_arg(ap, int);
767         rid = va_arg(ap, int);
768         r.top = y;
769         r.bottom = STATICHEIGHT;
770         y += r.bottom + GAPWITHIN;
771         r.left = rpos;
772         r.right = rwid / 2;
773         doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, ltext, lid);
774         r.left = rpos + r.right;
775         r.right = rwid - r.right;
776         doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_RIGHT, 0, "",
777               rid);
778     }
779     va_end(ap);
780
781     /* The button. */
782     r.top = y + 2 * GAPWITHIN;
783     r.bottom = PUSHBTNHEIGHT;
784     r.left = rpos;
785     r.right = rwid;
786     doctl(cp, r, "BUTTON",
787           WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
788           0, btext, bid);
789
790     cp->ypos += LISTHEIGHT + GAPBETWEEN;
791 }
792
793 /*
794  * A special control for manipulating an ordered preference list
795  * (eg. for cipher selection).
796  * XXX: this is a rough hack and could be improved.
797  */
798 void prefslist(struct prefslist *hdl, struct ctlpos *cp, char *stext,
799                int sid, int listid, int upbid, int dnbid)
800 {
801     const static int percents[] = { 5, 75, 20 };
802     RECT r;
803     int xpos, percent = 0, i;
804     const int DEFLISTHEIGHT = 52;      /* XXX configurable? */
805     const int BTNSHEIGHT = 2*PUSHBTNHEIGHT + GAPBETWEEN;
806     int totalheight;
807
808     /* Squirrel away IDs. */
809     hdl->listid = listid;
810     hdl->upbid  = upbid;
811     hdl->dnbid  = dnbid;
812
813     /* The static label. */
814     r.left = GAPBETWEEN;
815     r.top = cp->ypos;
816     r.right = cp->width;
817     r.bottom = STATICHEIGHT;
818     cp->ypos += r.bottom + GAPWITHIN;
819     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
820
821     /* XXX it'd be nice to centre the buttons wrt the listbox
822      * but we'd have to find out how high the latter actually is. */
823     if (DEFLISTHEIGHT > BTNSHEIGHT) {
824         totalheight = DEFLISTHEIGHT;
825     } else {
826         totalheight = BTNSHEIGHT;
827     }
828
829     for (i=0; i<3; i++) {
830         int left, wid;
831         xpos = (cp->width + GAPBETWEEN) * percent / 100;
832         left = xpos + GAPBETWEEN;
833         percent += percents[i];
834         xpos = (cp->width + GAPBETWEEN) * percent / 100;
835         wid = xpos - left;
836
837         switch (i) {
838           case 1:
839             /* The drag list box. */
840             r.left = left; r.right = wid;
841             r.top = cp->ypos; r.bottom = totalheight;
842             {
843                 HWND ctl;
844                 ctl = doctl(cp, r, "LISTBOX",
845                             WS_CHILD | WS_VISIBLE | WS_TABSTOP |
846                             WS_VSCROLL | LBS_HASSTRINGS,
847                             WS_EX_CLIENTEDGE,
848                             "", listid);
849                 MakeDragList(ctl);
850             }
851             break;
852
853           case 2:
854             /* The "Up" and "Down" buttons. */
855             /* XXX worry about accelerators if we have more than one
856              * prefslist on a panel */
857             r.left = left; r.right = wid;
858             r.top = cp->ypos; r.bottom = PUSHBTNHEIGHT;
859             doctl(cp, r, "BUTTON",
860                   WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
861                   0, "&Up", upbid);
862
863             r.left = left; r.right = wid;
864             r.top = cp->ypos + PUSHBTNHEIGHT + GAPBETWEEN;
865             r.bottom = PUSHBTNHEIGHT;
866             doctl(cp, r, "BUTTON",
867                   WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
868                   0, "&Down", dnbid);
869
870             break;
871
872         }
873     }
874
875     cp->ypos += totalheight + GAPBETWEEN;
876
877 }
878
879 /*
880  * Helper function for prefslist: move item in list box.
881  */
882 static void pl_moveitem(HWND hwnd, int listid, int src, int dst)
883 {
884     int tlen, val;
885     char *txt;
886     /* Get the item's data. */
887     tlen = SendDlgItemMessage (hwnd, listid, LB_GETTEXTLEN, src, 0);
888     txt = smalloc(tlen+1);
889     SendDlgItemMessage (hwnd, listid, LB_GETTEXT, src, (LPARAM) txt);
890     val = SendDlgItemMessage (hwnd, listid, LB_GETITEMDATA, src, 0);
891     /* Deselect old location. */
892     SendDlgItemMessage (hwnd, listid, LB_SETSEL, FALSE, src);
893     /* Delete it at the old location. */
894     SendDlgItemMessage (hwnd, listid, LB_DELETESTRING, src, 0);
895     /* Insert it at new location. */
896     SendDlgItemMessage (hwnd, listid, LB_INSERTSTRING, dst,
897                         (LPARAM) txt);
898     SendDlgItemMessage (hwnd, listid, LB_SETITEMDATA, dst,
899                         (LPARAM) val);
900     /* Set selection. */
901     SendDlgItemMessage (hwnd, listid, LB_SETCURSEL, dst, 0);
902     sfree (txt);
903 }
904
905 int pl_itemfrompt(HWND hwnd, POINT cursor, BOOL scroll)
906 {
907     int ret;
908     POINT uppoint, downpoint;
909     int updist, downdist, upitem, downitem, i;
910
911     /*
912      * Ghastly hackery to try to figure out not which
913      * _item_, but which _gap between items_, the user
914      * is pointing at. We do this by first working out
915      * which list item is under the cursor, and then
916      * working out how far the cursor would have to
917      * move up or down before the answer was different.
918      * Then we put the insertion point _above_ the
919      * current item if the upper edge is closer than
920      * the lower edge, or _below_ it if vice versa.
921      */
922     ret = LBItemFromPt(hwnd, cursor, scroll);
923     if (ret == -1)
924         return ret;
925     ret = LBItemFromPt(hwnd, cursor, FALSE);
926     updist = downdist = 0;
927     for (i = 1; i < 4096 && (!updist || !downdist); i++) {
928         uppoint = downpoint = cursor;
929         uppoint.y -= i;
930         downpoint.y += i;
931         upitem = LBItemFromPt(hwnd, uppoint, FALSE);
932         downitem = LBItemFromPt(hwnd, downpoint, FALSE);
933         if (!updist && upitem != ret)
934             updist = i;
935         if (!downdist && downitem != ret)
936             downdist = i;
937     }
938     if (downdist < updist)
939         ret++;
940     return ret;
941 }
942
943 /*
944  * Handler for prefslist above.
945  */
946 int handle_prefslist(struct prefslist *hdl,
947                      int *array, int maxmemb,
948                      int is_dlmsg, HWND hwnd,
949                      WPARAM wParam, LPARAM lParam)
950 {
951     int i;
952     int ret;
953
954     if (is_dlmsg) {
955
956         if (wParam == hdl->listid) {
957             DRAGLISTINFO *dlm = (DRAGLISTINFO *)lParam;
958             int dest;
959             switch (dlm->uNotification) {
960               case DL_BEGINDRAG:
961                 hdl->dummyitem =
962                     SendDlgItemMessage(hwnd, hdl->listid,
963                                        LB_ADDSTRING, 0, (LPARAM) "");
964
965                 hdl->srcitem = LBItemFromPt(dlm->hWnd, dlm->ptCursor, TRUE);
966                 hdl->dragging = 0;
967                 /* XXX hack Q183115 */
968                 SetWindowLong(hwnd, DWL_MSGRESULT, TRUE);
969                 ret = 1; break;
970               case DL_CANCELDRAG:
971                 DrawInsert(hwnd, dlm->hWnd, -1);     /* Clear arrow */
972                 SendDlgItemMessage(hwnd, hdl->listid,
973                                    LB_DELETESTRING, hdl->dummyitem, 0);
974                 hdl->dragging = 0;
975                 ret = 1; break;
976               case DL_DRAGGING:
977                 hdl->dragging = 1;
978                 dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, TRUE);
979                 if (dest > hdl->dummyitem) dest = hdl->dummyitem;
980                 DrawInsert (hwnd, dlm->hWnd, dest);
981                 if (dest >= 0)
982                     SetWindowLong(hwnd, DWL_MSGRESULT, DL_MOVECURSOR);
983                 else
984                     SetWindowLong(hwnd, DWL_MSGRESULT, DL_STOPCURSOR);
985                 ret = 1; break;
986               case DL_DROPPED:
987                 ret = 1;
988                 if (!hdl->dragging) break;
989                 dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, TRUE);
990                 if (dest > hdl->dummyitem) dest = hdl->dummyitem;
991                 DrawInsert (hwnd, dlm->hWnd, -1);
992                 SendDlgItemMessage(hwnd, hdl->listid,
993                                    LB_DELETESTRING, hdl->dummyitem, 0);
994                 hdl->dragging = 0;
995                 if (dest >= 0) {
996                     /* Correct for "missing" item. This means you can't drag
997                      * an item to the end, but that seems to be the way this
998                      * control is used. */
999                     if (dest > hdl->srcitem) dest--;
1000                     pl_moveitem(hwnd, hdl->listid, hdl->srcitem, dest);
1001                 }
1002                 ret = 1; break;
1003             }
1004         }
1005
1006     } else {
1007
1008         ret = 0;
1009         if (((LOWORD(wParam) == hdl->upbid) ||
1010              (LOWORD(wParam) == hdl->dnbid)) &&
1011             ((HIWORD(wParam) == BN_CLICKED) ||
1012              (HIWORD(wParam) == BN_DOUBLECLICKED))) {
1013             /* Move an item up or down the list. */
1014             /* Get the current selection, if any. */
1015             int selection = SendDlgItemMessage (hwnd, hdl->listid, LB_GETCURSEL, 0, 0);
1016             if (selection == LB_ERR) {
1017                 MessageBeep(0);
1018             } else {
1019                 int nitems;
1020                 /* Get the total number of items. */
1021                 nitems = SendDlgItemMessage (hwnd, hdl->listid, LB_GETCOUNT, 0, 0);
1022                 /* Should we do anything? */
1023                 if (LOWORD(wParam) == hdl->upbid && (selection > 0))
1024                     pl_moveitem(hwnd, hdl->listid, selection, selection - 1);
1025                 else if (LOWORD(wParam) == hdl->dnbid && (selection < nitems - 1))
1026                     pl_moveitem(hwnd, hdl->listid, selection, selection + 1);
1027             }
1028
1029         }
1030
1031     }
1032
1033     /* Update array to match the list box. */
1034     for (i=0; i < maxmemb; i++)
1035         array[i] = SendDlgItemMessage (hwnd, hdl->listid, LB_GETITEMDATA,
1036                                        i, 0);
1037
1038     return ret;
1039
1040 }
1041
1042 /*
1043  * A progress bar (from Common Controls). We like our progress bars
1044  * to be smooth and unbroken, without those ugly divisions; some
1045  * older compilers may not support that, but that's life.
1046  */
1047 void progressbar(struct ctlpos *cp, int id)
1048 {
1049     RECT r;
1050
1051     r.left = GAPBETWEEN;
1052     r.top = cp->ypos;
1053     r.right = cp->width;
1054     r.bottom = PROGBARHEIGHT;
1055     cp->ypos += r.bottom + GAPBETWEEN;
1056
1057     doctl(cp, r, PROGRESS_CLASS, WS_CHILD | WS_VISIBLE
1058 #ifdef PBS_SMOOTH
1059           | PBS_SMOOTH
1060 #endif
1061           , WS_EX_CLIENTEDGE, "", id);
1062 }
1063
1064 /*
1065  * Another special control: the forwarding options setter. First a
1066  * list box; next a static header line, introducing a pair of edit
1067  * boxes with associated statics, another button, and a radio
1068  * button pair.
1069  */
1070 void fwdsetter(struct ctlpos *cp, int listid, char *stext, int sid,
1071                char *e1stext, int e1sid, int e1id,
1072                char *e2stext, int e2sid, int e2id,
1073                char *btext, int bid)
1074 {
1075     RECT r;
1076     const int height = (STATICHEIGHT > EDITHEIGHT
1077                         && STATICHEIGHT >
1078                         PUSHBTNHEIGHT ? STATICHEIGHT : EDITHEIGHT >
1079                         PUSHBTNHEIGHT ? EDITHEIGHT : PUSHBTNHEIGHT);
1080     const static int percents[] = { 25, 35, 15, 25 };
1081     int i, j, xpos, percent;
1082     const int LISTHEIGHT = 42;
1083
1084     /* The list box. */
1085     r.left = GAPBETWEEN;
1086     r.top = cp->ypos;
1087     r.right = cp->width;
1088     r.bottom = LISTHEIGHT;
1089     cp->ypos += r.bottom + GAPBETWEEN;
1090     doctl(cp, r, "LISTBOX",
1091           WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | LBS_HASSTRINGS
1092           | LBS_USETABSTOPS, WS_EX_CLIENTEDGE, "", listid);
1093
1094     /* The static control. */
1095     r.left = GAPBETWEEN;
1096     r.top = cp->ypos;
1097     r.right = cp->width;
1098     r.bottom = STATICHEIGHT;
1099     cp->ypos += r.bottom + GAPWITHIN;
1100     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
1101
1102     /* The statics+edits+buttons. */
1103     for (j = 0; j < 2; j++) {
1104         percent = 0;
1105         for (i = 0; i < (j ? 2 : 4); i++) {
1106             xpos = (cp->width + GAPBETWEEN) * percent / 100;
1107             r.left = xpos + GAPBETWEEN;
1108             percent += percents[i];
1109             if (j==1 && i==1) percent = 100;
1110             xpos = (cp->width + GAPBETWEEN) * percent / 100;
1111             r.right = xpos - r.left;
1112             r.top = cp->ypos;
1113             r.bottom = (i == 0 ? STATICHEIGHT :
1114                         i == 1 ? EDITHEIGHT : PUSHBTNHEIGHT);
1115             r.top += (height - r.bottom) / 2;
1116             if (i == 0) {
1117                 doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0,
1118                       j == 0 ? e1stext : e2stext, j == 0 ? e1sid : e2sid);
1119             } else if (i == 1) {
1120                 doctl(cp, r, "EDIT",
1121                       WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
1122                       WS_EX_CLIENTEDGE, "", j == 0 ? e1id : e2id);
1123             } else if (i == 3) {
1124                 doctl(cp, r, "BUTTON",
1125                       WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
1126                       0, btext, bid);
1127             }
1128         }
1129         cp->ypos += height + GAPWITHIN;
1130     }
1131 }