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