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