]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - winctrls.c
Fix various slashes.
[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
11 #define GAPBETWEEN 3
12 #define GAPWITHIN 1
13 #define GAPXBOX 7
14 #define GAPYBOX 4
15 #define DLGWIDTH 168
16 #define STATICHEIGHT 8
17 #define CHECKBOXHEIGHT 8
18 #define RADIOHEIGHT 8
19 #define EDITHEIGHT 12
20 #define COMBOHEIGHT 12
21 #define PUSHBTNHEIGHT 14
22 #define PROGBARHEIGHT 14
23
24 void ctlposinit(struct ctlpos *cp, HWND hwnd,
25                 int leftborder, int rightborder, int topborder) {
26     RECT r, r2;
27     cp->hwnd = hwnd;
28     cp->font = SendMessage(hwnd, WM_GETFONT, 0, 0);
29     cp->ypos = topborder;
30     GetClientRect(hwnd, &r);
31     r2.left = r2.top = 0;
32     r2.right = 4;
33     r2.bottom = 8;
34     MapDialogRect(hwnd, &r2);
35     cp->dlu4inpix = r2.right;
36     cp->width = (r.right * 4) / (r2.right) - 2*GAPBETWEEN;
37     cp->xoff = leftborder;
38     cp->width -= leftborder + rightborder;
39 }
40
41 void doctl(struct ctlpos *cp, RECT r,
42            char *wclass, int wstyle, int exstyle,
43            char *wtext, int wid) {
44     HWND ctl;
45     /*
46      * Note nonstandard use of RECT. This is deliberate: by
47      * transforming the width and height directly we arrange to
48      * have all supposedly same-sized controls really same-sized.
49      */
50
51     r.left += cp->xoff;
52     MapDialogRect(cp->hwnd, &r);
53
54     ctl = CreateWindowEx(exstyle, wclass, wtext, wstyle,
55                          r.left, r.top, r.right, r.bottom,
56                          cp->hwnd, (HMENU)wid, hinst, NULL);
57     SendMessage(ctl, WM_SETFONT, cp->font, MAKELPARAM(TRUE, 0));
58 }
59
60 /*
61  * A title bar across the top of a sub-dialog.
62  */
63 void bartitle(struct ctlpos *cp, char *name, int id) {
64     RECT r;
65
66     r.left = GAPBETWEEN; r.right = cp->width;
67     r.top = cp->ypos; r.bottom = STATICHEIGHT;
68     cp->ypos += r.bottom + GAPBETWEEN;
69     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, name, id);
70 }
71
72 /*
73  * Begin a grouping box, with or without a group title.
74  */
75 void beginbox(struct ctlpos *cp, char *name, int idbox) {
76     cp->boxystart = cp->ypos;
77     if (!name)
78         cp->boxystart -= STATICHEIGHT/2;
79     if (name)
80         cp->ypos += STATICHEIGHT;
81     cp->ypos += GAPYBOX;
82     cp->width -= 2*GAPXBOX;
83     cp->xoff += GAPXBOX;
84     cp->boxid = idbox;
85     cp->boxtext = name;
86 }
87
88 /*
89  * End a grouping box.
90  */
91 void endbox(struct ctlpos *cp) {
92     RECT r;
93     cp->xoff -= GAPXBOX;
94     cp->width += 2*GAPXBOX;
95     cp->ypos += GAPYBOX - GAPBETWEEN;
96     r.left = GAPBETWEEN; r.right = cp->width;
97     r.top = cp->boxystart; r.bottom = cp->ypos - cp->boxystart;
98     doctl(cp, r, "BUTTON", BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 0,
99           cp->boxtext ? cp->boxtext : "", cp->boxid);
100     cp->ypos += GAPYBOX;
101 }
102
103 /*
104  * Some edit boxes. Each one has a static above it. The percentages
105  * of the horizontal space are provided.
106  */
107 void multiedit(struct ctlpos *cp, ...) {
108     RECT r;
109     va_list ap;
110     int percent, xpos;
111
112     percent = xpos = 0;
113     va_start(ap, cp);
114     while (1) {
115         char *text;
116         int staticid, editid, pcwidth;
117         text = va_arg(ap, char *);
118         if (!text)
119             break;
120         staticid = va_arg(ap, int);
121         editid = va_arg(ap, int);
122         pcwidth = va_arg(ap, int);
123
124         r.left = xpos + GAPBETWEEN;
125         percent += pcwidth;
126         xpos = (cp->width + GAPBETWEEN) * percent / 100;
127         r.right = xpos - r.left;
128
129         r.top = cp->ypos; r.bottom = STATICHEIGHT;
130         doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0,
131               text, staticid);
132         r.top = cp->ypos + 8 + GAPWITHIN; r.bottom = EDITHEIGHT;
133         doctl(cp, r, "EDIT",
134               WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
135               WS_EX_CLIENTEDGE,
136               "", editid);
137     }
138     va_end(ap);
139     cp->ypos += 8+GAPWITHIN+12+GAPBETWEEN;
140 }
141
142 /*
143  * A set of radio buttons on the same line, with a static above
144  * them. `nacross' dictates how many parts the line is divided into
145  * (you might want this not to equal the number of buttons if you
146  * needed to line up some 2s and some 3s to look good in the same
147  * panel).
148  */
149 void radioline(struct ctlpos *cp,
150                char *text, int id, int nacross, ...) {
151     RECT r;
152     va_list ap;
153     int group;
154     int i;
155
156     r.left = GAPBETWEEN; r.top = cp->ypos;
157     r.right = cp->width; r.bottom = STATICHEIGHT;
158     cp->ypos += r.bottom + GAPWITHIN;
159     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
160     va_start(ap, nacross);
161     group = WS_GROUP;
162     i = 0;
163     while (1) {
164         char *btext;
165         int bid;
166         btext = va_arg(ap, char *);
167         if (!btext)
168             break;
169         bid = va_arg(ap, int);
170         r.left = GAPBETWEEN + i * (cp->width+GAPBETWEEN)/nacross;
171         r.right = (i+1) * (cp->width+GAPBETWEEN)/nacross - r.left;
172         r.top = cp->ypos; r.bottom = RADIOHEIGHT;
173         doctl(cp, r, "BUTTON",
174               BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP | group,
175               0,
176               btext, bid);
177         group = 0;
178         i++;
179     }
180     va_end(ap);
181     cp->ypos += r.bottom + GAPBETWEEN;
182 }
183
184 /*
185  * A set of radio buttons on multiple lines, with a static above
186  * them.
187  */
188 void radiobig(struct ctlpos *cp, char *text, int id, ...) {
189     RECT r;
190     va_list ap;
191     int group;
192
193     r.left = GAPBETWEEN; r.top = cp->ypos;
194     r.right = cp->width; r.bottom = STATICHEIGHT;
195     cp->ypos += r.bottom + GAPWITHIN;
196     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
197     va_start(ap, id);
198     group = WS_GROUP;
199     while (1) {
200         char *btext;
201         int bid;
202         btext = va_arg(ap, char *);
203         if (!btext)
204             break;
205         bid = va_arg(ap, int);
206         r.left = GAPBETWEEN; r.top = cp->ypos;
207         r.right = cp->width; r.bottom = STATICHEIGHT;
208         cp->ypos += r.bottom + GAPWITHIN;
209         doctl(cp, r, "BUTTON",
210               BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP | group,
211               0,
212               btext, bid);
213         group = 0;
214     }
215     va_end(ap);
216     cp->ypos += GAPBETWEEN - GAPWITHIN;
217 }
218
219 /*
220  * A single standalone checkbox.
221  */
222 void checkbox(struct ctlpos *cp, char *text, int id) {
223     RECT r;
224
225     r.left = GAPBETWEEN; r.top = cp->ypos;
226     r.right = cp->width; r.bottom = CHECKBOXHEIGHT;
227     cp->ypos += r.bottom + GAPBETWEEN;
228     doctl(cp, r, "BUTTON",
229           BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0,
230           text, id);
231 }
232
233 /*
234  * A single standalone static text control.
235  */
236 void statictext(struct ctlpos *cp, char *text, int id) {
237     RECT r;
238
239     r.left = GAPBETWEEN; r.top = cp->ypos;
240     r.right = cp->width; r.bottom = STATICHEIGHT;
241     cp->ypos += r.bottom + GAPBETWEEN;
242     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
243 }
244
245 /*
246  * A button on the right hand side, with a static to its left.
247  */
248 void staticbtn(struct ctlpos *cp, char *stext, int sid,
249                char *btext, int bid) {
250     const int height = (PUSHBTNHEIGHT > STATICHEIGHT ?
251                         PUSHBTNHEIGHT : STATICHEIGHT);
252     RECT r;
253     int lwid, rwid, rpos;
254
255     rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
256     lwid = rpos - 2*GAPBETWEEN;
257     rwid = cp->width + GAPBETWEEN - rpos;
258
259     r.left = GAPBETWEEN; r.top = cp->ypos + (height-STATICHEIGHT)/2;
260     r.right = lwid; r.bottom = STATICHEIGHT;
261     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
262
263     r.left = rpos; r.top = cp->ypos + (height-PUSHBTNHEIGHT)/2;
264     r.right = rwid; r.bottom = PUSHBTNHEIGHT;
265     doctl(cp, r, "BUTTON",
266           WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
267           0,
268           btext, bid);
269
270     cp->ypos += height + GAPBETWEEN;
271 }
272
273 /*
274  * An edit control on the right hand side, with a static to its left.
275  */
276 static void staticedit_internal(struct ctlpos *cp, char *stext,
277                                 int sid, int eid, int percentedit,
278                                 int style) {
279     const int height = (EDITHEIGHT > STATICHEIGHT ?
280                         EDITHEIGHT : STATICHEIGHT);
281     RECT r;
282     int lwid, rwid, rpos;
283
284     rpos = GAPBETWEEN + (100-percentedit) * (cp->width + GAPBETWEEN) / 100;
285     lwid = rpos - 2*GAPBETWEEN;
286     rwid = cp->width + GAPBETWEEN - rpos;
287
288     r.left = GAPBETWEEN; r.top = cp->ypos + (height-STATICHEIGHT)/2;
289     r.right = lwid; r.bottom = STATICHEIGHT;
290     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
291
292     r.left = rpos; r.top = cp->ypos + (height-EDITHEIGHT)/2;
293     r.right = rwid; r.bottom = EDITHEIGHT;
294     doctl(cp, r, "EDIT",
295           WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | style,
296           WS_EX_CLIENTEDGE,
297           "", eid);
298
299     cp->ypos += height + GAPBETWEEN;
300 }
301
302 void staticedit(struct ctlpos *cp, char *stext,
303                 int sid, int eid, int percentedit) {
304     staticedit_internal(cp, stext, sid, eid, percentedit, 0);
305 }
306
307 void staticpassedit(struct ctlpos *cp, char *stext,
308                     int sid, int eid, int percentedit) {
309     staticedit_internal(cp, stext, sid, eid, percentedit, ES_PASSWORD);
310 }
311
312 /*
313  * A big multiline edit control with a static labelling it.
314  */
315 void bigeditctrl(struct ctlpos *cp, char *stext,
316                  int sid, int eid, int lines) {
317     RECT r;
318
319     r.left = GAPBETWEEN; r.top = cp->ypos;
320     r.right = cp->width; r.bottom = STATICHEIGHT;
321     cp->ypos += r.bottom + GAPWITHIN;
322     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
323
324     r.left = GAPBETWEEN; r.top = cp->ypos;
325     r.right = cp->width; r.bottom = EDITHEIGHT + (lines-1) * STATICHEIGHT;
326     cp->ypos += r.bottom + GAPBETWEEN;
327     doctl(cp, r, "EDIT",
328           WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | ES_MULTILINE,
329           WS_EX_CLIENTEDGE,
330           "", eid);
331 }
332
333 /*
334  * A tab-control substitute when a real tab control is unavailable.
335  */
336 void ersatztab(struct ctlpos *cp, char *stext, int sid,
337                int lid, int s2id) {
338     const int height = (COMBOHEIGHT > STATICHEIGHT ?
339                         COMBOHEIGHT : STATICHEIGHT);
340     RECT r;
341     int bigwid, lwid, rwid, rpos;
342     static const int BIGGAP = 15;
343     static const int MEDGAP = 3;
344
345     bigwid = cp->width + 2*GAPBETWEEN - 2*BIGGAP;
346     cp->ypos += MEDGAP;
347     rpos = BIGGAP + (bigwid + BIGGAP) / 2;
348     lwid = rpos - 2*BIGGAP;
349     rwid = bigwid + BIGGAP - rpos;
350
351     r.left = BIGGAP; r.top = cp->ypos + (height-STATICHEIGHT)/2;
352     r.right = lwid; r.bottom = STATICHEIGHT;
353     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
354
355     r.left = rpos; r.top = cp->ypos + (height-COMBOHEIGHT)/2;
356     r.right = rwid; r.bottom = COMBOHEIGHT*10;
357     doctl(cp, r, "COMBOBOX",
358           WS_CHILD | WS_VISIBLE | WS_TABSTOP |
359           CBS_DROPDOWNLIST | CBS_HASSTRINGS,
360           WS_EX_CLIENTEDGE,
361           "", lid);
362
363     cp->ypos += height + MEDGAP + GAPBETWEEN;
364
365     r.left = GAPBETWEEN; r.top = cp->ypos;
366     r.right = cp->width; r.bottom = 2;
367     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
368           0, "", s2id);
369 }
370
371 /*
372  * A static line, followed by an edit control on the left hand side
373  * and a button on the right.
374  */
375 void editbutton(struct ctlpos *cp, char *stext, int sid,
376                 int eid, char *btext, int bid) {
377     const int height = (EDITHEIGHT > PUSHBTNHEIGHT ?
378                         EDITHEIGHT : PUSHBTNHEIGHT);
379     RECT r;
380     int lwid, rwid, rpos;
381
382     r.left = GAPBETWEEN; r.top = cp->ypos;
383     r.right = cp->width; r.bottom = STATICHEIGHT;
384     cp->ypos += r.bottom + GAPWITHIN;
385     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
386
387     rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
388     lwid = rpos - 2*GAPBETWEEN;
389     rwid = cp->width + GAPBETWEEN - rpos;
390
391     r.left = GAPBETWEEN; r.top = cp->ypos + (height-EDITHEIGHT)/2;
392     r.right = lwid; r.bottom = EDITHEIGHT;
393     doctl(cp, r, "EDIT",
394           WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
395           WS_EX_CLIENTEDGE,
396           "", eid);
397
398     r.left = rpos; r.top = cp->ypos + (height-PUSHBTNHEIGHT)/2;
399     r.right = rwid; r.bottom = PUSHBTNHEIGHT;
400     doctl(cp, r, "BUTTON",
401           WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
402           0,
403           btext, bid);
404
405     cp->ypos += height + GAPBETWEEN;
406 }
407
408 /*
409  * Special control which was hard to describe generically: the
410  * session-saver assembly. A static; below that an edit box; below
411  * that a list box. To the right of the list box, a column of
412  * buttons.
413  */
414 void sesssaver(struct ctlpos *cp, char *text,
415                int staticid, int editid, int listid, ...) {
416     RECT r;
417     va_list ap;
418     int lwid, rwid, rpos;
419     int y;
420     const int LISTDEFHEIGHT = 66;
421
422     rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
423     lwid = rpos - 2*GAPBETWEEN;
424     rwid = cp->width + GAPBETWEEN - rpos;
425
426     /* The static control. */
427     r.left = GAPBETWEEN; r.top = cp->ypos;
428     r.right = lwid; r.bottom = STATICHEIGHT;
429     cp->ypos += r.bottom + GAPWITHIN;
430     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, staticid);
431
432     /* The edit control. */
433     r.left = GAPBETWEEN; r.top = cp->ypos;
434     r.right = lwid; r.bottom = EDITHEIGHT;
435     cp->ypos += r.bottom + GAPWITHIN;
436     doctl(cp, r, "EDIT",
437           WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
438           WS_EX_CLIENTEDGE,
439           "", editid);
440
441     /*
442      * The buttons (we should hold off on the list box until we
443      * know how big the buttons are).
444      */
445     va_start(ap, listid);
446     y = cp->ypos;
447     while (1) {
448         char *btext = va_arg(ap, char *);
449         int bid;
450         if (!btext) break;
451         bid = va_arg(ap, int);
452         r.left = rpos; r.top = y;
453         r.right = rwid; r.bottom = PUSHBTNHEIGHT;
454         y += r.bottom + GAPWITHIN;
455         doctl(cp, r, "BUTTON",
456               WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
457               0,
458               btext, bid);
459     }
460
461     /* Compute list box height. LISTDEFHEIGHT, or height of buttons. */
462     y -= cp->ypos;
463     y -= GAPWITHIN;
464     if (y < LISTDEFHEIGHT) y = LISTDEFHEIGHT;
465     r.left = GAPBETWEEN; r.top = cp->ypos;
466     r.right = lwid; r.bottom = y;
467     cp->ypos += y + GAPBETWEEN;
468     doctl(cp, r, "LISTBOX",
469           WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | 
470           LBS_NOTIFY | LBS_HASSTRINGS,
471           WS_EX_CLIENTEDGE,
472           "", listid);
473 }
474
475 /*
476  * Another special control: the environment-variable setter. A
477  * static line first; then a pair of edit boxes with associated
478  * statics, and two buttons; then a list box.
479  */
480 void envsetter(struct ctlpos *cp, char *stext, int sid,
481                char *e1stext, int e1sid, int e1id,
482                char *e2stext, int e2sid, int e2id,
483                int listid,
484                char *b1text, int b1id, char *b2text, int b2id) {
485     RECT r;
486     const int height = (STATICHEIGHT > EDITHEIGHT && STATICHEIGHT > PUSHBTNHEIGHT ?
487                         STATICHEIGHT :
488                         EDITHEIGHT > PUSHBTNHEIGHT ?
489                         EDITHEIGHT : PUSHBTNHEIGHT);
490     const static int percents[] = { 20, 35, 10, 25 };
491     int i, j, xpos, percent;
492     const int LISTHEIGHT = 42;
493
494     /* The static control. */
495     r.left = GAPBETWEEN; r.top = cp->ypos;
496     r.right = cp->width; r.bottom = STATICHEIGHT;
497     cp->ypos += r.bottom + GAPWITHIN;
498     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
499
500     /* The statics+edits+buttons. */
501     for (j = 0; j < 2; j++) {
502         percent = 10;
503         for (i = 0; i < 4; i++) {
504             xpos = (cp->width + GAPBETWEEN) * percent / 100;
505             r.left = xpos + GAPBETWEEN;
506             percent += percents[i];
507             xpos = (cp->width + GAPBETWEEN) * percent / 100;
508             r.right = xpos - r.left;
509             r.top = cp->ypos;
510             r.bottom = (i==0 ? STATICHEIGHT :
511                         i==1 ? EDITHEIGHT :
512                         PUSHBTNHEIGHT);
513             r.top += (height-r.bottom)/2;
514             if (i==0) {
515                 doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0,
516                       j==0 ? e1stext : e2stext, j==0 ? e1sid : e2sid);
517             } else if (i==1) {
518                 doctl(cp, r, "EDIT",
519                       WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
520                       WS_EX_CLIENTEDGE,
521                       "", j==0 ? e1id : e2id);
522             } else if (i==3) {
523                 doctl(cp, r, "BUTTON",
524                       WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
525                       0,
526                       j==0 ? b1text : b2text, j==0 ? b1id : b2id);
527             }
528         }
529         cp->ypos += height + GAPWITHIN;
530     }
531
532     /* The list box. */
533     r.left = GAPBETWEEN; r.top = cp->ypos;
534     r.right = cp->width; r.bottom = LISTHEIGHT;
535     cp->ypos += r.bottom + GAPBETWEEN;
536     doctl(cp, r, "LISTBOX",
537           WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | LBS_HASSTRINGS |
538           LBS_USETABSTOPS,
539           WS_EX_CLIENTEDGE,
540           "", listid);
541 }
542
543 /*
544  * Yet another special control: the character-class setter. A
545  * static, then a list, then a line containing a
546  * button-and-static-and-edit. 
547  */
548 void charclass(struct ctlpos *cp, char *stext, int sid, int listid,
549                char *btext, int bid, int eid, char *s2text, int s2id) {
550     RECT r;
551     const int height = (STATICHEIGHT > EDITHEIGHT && STATICHEIGHT > PUSHBTNHEIGHT ?
552                         STATICHEIGHT :
553                         EDITHEIGHT > PUSHBTNHEIGHT ?
554                         EDITHEIGHT : PUSHBTNHEIGHT);
555     const static int percents[] = { 30, 40, 30 };
556     int i, xpos, percent;
557     const int LISTHEIGHT = 66;
558
559     /* The static control. */
560     r.left = GAPBETWEEN; r.top = cp->ypos;
561     r.right = cp->width; r.bottom = STATICHEIGHT;
562     cp->ypos += r.bottom + GAPWITHIN;
563     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
564
565     /* The list box. */
566     r.left = GAPBETWEEN; r.top = cp->ypos;
567     r.right = cp->width; r.bottom = LISTHEIGHT;
568     cp->ypos += r.bottom + GAPWITHIN;
569     doctl(cp, r, "LISTBOX",
570           WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | LBS_HASSTRINGS |
571           LBS_USETABSTOPS,
572           WS_EX_CLIENTEDGE,
573           "", listid);
574
575     /* The button+static+edit. */
576     percent = xpos = 0;
577     for (i = 0; i < 3; i++) {
578         r.left = xpos + GAPBETWEEN;
579         percent += percents[i];
580         xpos = (cp->width + GAPBETWEEN) * percent / 100;
581         r.right = xpos - r.left;
582         r.top = cp->ypos;
583         r.bottom = (i==0 ? PUSHBTNHEIGHT :
584                     i==1 ? STATICHEIGHT :
585                     EDITHEIGHT);
586         r.top += (height-r.bottom)/2;
587         if (i==0) {
588             doctl(cp, r, "BUTTON",
589                   WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
590                   0, btext, bid);
591         } else if (i==1) {
592             doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_CENTER,
593                   0, s2text, s2id);
594         } else if (i==2) {
595             doctl(cp, r, "EDIT",
596                   WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
597                   WS_EX_CLIENTEDGE, "", eid);
598         }
599     }
600     cp->ypos += height + GAPBETWEEN;
601 }
602
603 /*
604  * A special control (horrors!). The colour editor. A static line;
605  * then on the left, a list box, and on the right, a sequence of
606  * two-part statics followed by a button.
607  */
608 void colouredit(struct ctlpos *cp, char *stext, int sid, int listid,
609                 char *btext, int bid, ...) {
610     RECT r;
611     int y;
612     va_list ap;
613     int lwid, rwid, rpos;
614     const int LISTHEIGHT = 66;
615
616     /* The static control. */
617     r.left = GAPBETWEEN; r.top = cp->ypos;
618     r.right = cp->width; r.bottom = STATICHEIGHT;
619     cp->ypos += r.bottom + GAPWITHIN;
620     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
621     
622     rpos = GAPBETWEEN + 2 * (cp->width + GAPBETWEEN) / 3;
623     lwid = rpos - 2*GAPBETWEEN;
624     rwid = cp->width + GAPBETWEEN - rpos;
625
626     /* The list box. */
627     r.left = GAPBETWEEN; r.top = cp->ypos;
628     r.right = lwid; r.bottom = LISTHEIGHT;
629     doctl(cp, r, "LISTBOX",
630           WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | LBS_HASSTRINGS |
631           LBS_USETABSTOPS | LBS_NOTIFY,
632           WS_EX_CLIENTEDGE,
633           "", listid);
634
635     /* The statics. */
636     y = cp->ypos;
637     va_start(ap, bid);
638     while (1) {
639         char *ltext;
640         int lid, rid;
641         ltext = va_arg(ap, char *);
642         if (!ltext) break;
643         lid = va_arg(ap, int);
644         rid = va_arg(ap, int);
645         r.top = y; r.bottom = STATICHEIGHT;
646         y += r.bottom + GAPWITHIN;
647         r.left = rpos; r.right = rwid/2;
648         doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, ltext, lid);
649         r.left = rpos + r.right; r.right = rwid - r.right;
650         doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_RIGHT, 0, "", rid);
651     }
652     va_end(ap);
653
654     /* The button. */
655     r.top = y + 2*GAPWITHIN; r.bottom = PUSHBTNHEIGHT;
656     r.left = rpos; r.right = rwid;
657     doctl(cp, r, "BUTTON",
658           WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
659           0, btext, bid);
660
661     cp->ypos += LISTHEIGHT + GAPBETWEEN;
662 }
663
664 /*
665  * A progress bar (from Common Controls). We like our progress bars
666  * to be smooth and unbroken, without those ugly divisions; some
667  * older compilers may not support that, but that's life.
668  */
669 void progressbar(struct ctlpos *cp, int id) {
670     RECT r;
671
672     r.left = GAPBETWEEN; r.top = cp->ypos;
673     r.right = cp->width; r.bottom = PROGBARHEIGHT;
674     cp->ypos += r.bottom + GAPBETWEEN;
675
676     doctl(cp, r, PROGRESS_CLASS,
677           WS_CHILD | WS_VISIBLE
678 #ifdef PBS_SMOOTH
679           | PBS_SMOOTH
680 #endif
681           , WS_EX_CLIENTEDGE, "", id);
682 }