]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - winctrls.c
PSCP in SFTP mode now uses the fast download/upload manager.
[PuTTY.git] / winctrls.c
1 /*
2  * winctrls.c: routines to self-manage the controls in a dialog
3  * box.
4  */
5
6 /*
7  * Possible TODO in new cross-platform config box stuff:
8  *
9  *  - When lining up two controls alongside each other, I wonder if
10  *    we could conveniently arrange to centre them vertically?
11  *    Particularly ugly in the current setup is the `Add new
12  *    forwarded port:' static next to the rather taller `Remove'
13  *    button.
14  */
15
16 #include <windows.h>
17 #include <commctrl.h>
18 #include <assert.h>
19 #include <ctype.h>
20
21 #include "winstuff.h"
22 #include "misc.h"
23 #include "dialog.h"
24 #include "puttymem.h"
25
26 #include "putty.h"
27
28 #define GAPBETWEEN 3
29 #define GAPWITHIN 1
30 #define GAPXBOX 7
31 #define GAPYBOX 4
32 #define DLGWIDTH 168
33 #define STATICHEIGHT 8
34 #define TITLEHEIGHT 12
35 #define CHECKBOXHEIGHT 8
36 #define RADIOHEIGHT 8
37 #define EDITHEIGHT 12
38 #define LISTHEIGHT 11
39 #define LISTINCREMENT 8
40 #define COMBOHEIGHT 12
41 #define PUSHBTNHEIGHT 14
42 #define PROGBARHEIGHT 14
43
44 void ctlposinit(struct ctlpos *cp, HWND hwnd,
45                 int leftborder, int rightborder, int topborder)
46 {
47     RECT r, r2;
48     cp->hwnd = hwnd;
49     cp->font = SendMessage(hwnd, WM_GETFONT, 0, 0);
50     cp->ypos = topborder;
51     GetClientRect(hwnd, &r);
52     r2.left = r2.top = 0;
53     r2.right = 4;
54     r2.bottom = 8;
55     MapDialogRect(hwnd, &r2);
56     cp->dlu4inpix = r2.right;
57     cp->width = (r.right * 4) / (r2.right) - 2 * GAPBETWEEN;
58     cp->xoff = leftborder;
59     cp->width -= leftborder + rightborder;
60 }
61
62 HWND doctl(struct ctlpos *cp, RECT r,
63            char *wclass, int wstyle, int exstyle, char *wtext, int wid)
64 {
65     HWND ctl;
66     /*
67      * Note nonstandard use of RECT. This is deliberate: by
68      * transforming the width and height directly we arrange to
69      * have all supposedly same-sized controls really same-sized.
70      */
71
72     r.left += cp->xoff;
73     MapDialogRect(cp->hwnd, &r);
74
75     /*
76      * We can pass in cp->hwnd == NULL, to indicate a dry run
77      * without creating any actual controls.
78      */
79     if (cp->hwnd) {
80         ctl = CreateWindowEx(exstyle, wclass, wtext, wstyle,
81                              r.left, r.top, r.right, r.bottom,
82                              cp->hwnd, (HMENU) wid, hinst, NULL);
83         SendMessage(ctl, WM_SETFONT, cp->font, MAKELPARAM(TRUE, 0));
84
85         if (!strcmp(wclass, "LISTBOX")) {
86             /*
87              * Bizarre Windows bug: the list box calculates its
88              * number of lines based on the font it has at creation
89              * time, but sending it WM_SETFONT doesn't cause it to
90              * recalculate. So now, _after_ we've sent it
91              * WM_SETFONT, we explicitly resize it (to the same
92              * size it was already!) to force it to reconsider.
93              */
94             SetWindowPos(ctl, NULL, 0, 0, r.right, r.bottom,
95                          SWP_NOACTIVATE | SWP_NOCOPYBITS |
96                          SWP_NOMOVE | SWP_NOZORDER);
97         }
98     } else
99         ctl = NULL;
100     return ctl;
101 }
102
103 /*
104  * A title bar across the top of a sub-dialog.
105  */
106 void bartitle(struct ctlpos *cp, char *name, int id)
107 {
108     RECT r;
109
110     r.left = GAPBETWEEN;
111     r.right = cp->width;
112     r.top = cp->ypos;
113     r.bottom = STATICHEIGHT;
114     cp->ypos += r.bottom + GAPBETWEEN;
115     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, name, id);
116 }
117
118 /*
119  * Begin a grouping box, with or without a group title.
120  */
121 void beginbox(struct ctlpos *cp, char *name, int idbox)
122 {
123     cp->boxystart = cp->ypos;
124     if (!name)
125         cp->boxystart -= STATICHEIGHT / 2;
126     if (name)
127         cp->ypos += STATICHEIGHT;
128     cp->ypos += GAPYBOX;
129     cp->width -= 2 * GAPXBOX;
130     cp->xoff += GAPXBOX;
131     cp->boxid = idbox;
132     cp->boxtext = name;
133 }
134
135 /*
136  * End a grouping box.
137  */
138 void endbox(struct ctlpos *cp)
139 {
140     RECT r;
141     cp->xoff -= GAPXBOX;
142     cp->width += 2 * GAPXBOX;
143     cp->ypos += GAPYBOX - GAPBETWEEN;
144     r.left = GAPBETWEEN;
145     r.right = cp->width;
146     r.top = cp->boxystart;
147     r.bottom = cp->ypos - cp->boxystart;
148     doctl(cp, r, "BUTTON", BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 0,
149           cp->boxtext ? cp->boxtext : "", cp->boxid);
150     cp->ypos += GAPYBOX;
151 }
152
153 /*
154  * Some edit boxes. Each one has a static above it. The percentages
155  * of the horizontal space are provided.
156  */
157 void multiedit(struct ctlpos *cp, int password, ...)
158 {
159     RECT r;
160     va_list ap;
161     int percent, xpos;
162
163     percent = xpos = 0;
164     va_start(ap, password);
165     while (1) {
166         char *text;
167         int staticid, editid, pcwidth;
168         text = va_arg(ap, char *);
169         if (!text)
170             break;
171         staticid = va_arg(ap, int);
172         editid = va_arg(ap, int);
173         pcwidth = va_arg(ap, int);
174
175         r.left = xpos + GAPBETWEEN;
176         percent += pcwidth;
177         xpos = (cp->width + GAPBETWEEN) * percent / 100;
178         r.right = xpos - r.left;
179
180         r.top = cp->ypos;
181         r.bottom = STATICHEIGHT;
182         doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, staticid);
183         r.top = cp->ypos + 8 + GAPWITHIN;
184         r.bottom = EDITHEIGHT;
185         doctl(cp, r, "EDIT",
186               WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL |
187               (password ? ES_PASSWORD : 0),
188               WS_EX_CLIENTEDGE, "", editid);
189     }
190     va_end(ap);
191     cp->ypos += STATICHEIGHT + GAPWITHIN + EDITHEIGHT + GAPBETWEEN;
192 }
193
194 /*
195  * A static line, followed by a full-width combo box.
196  */
197 void combobox(struct ctlpos *cp, char *text, int staticid, int listid)
198 {
199     RECT r;
200
201     r.left = GAPBETWEEN;
202     r.right = cp->width;
203
204     r.top = cp->ypos;
205     r.bottom = STATICHEIGHT;
206     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, staticid);
207     r.top = cp->ypos + 8 + GAPWITHIN;
208     r.bottom = COMBOHEIGHT * 10;
209     doctl(cp, r, "COMBOBOX",
210           WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
211           CBS_DROPDOWN | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", listid);
212
213     cp->ypos += STATICHEIGHT + GAPWITHIN + COMBOHEIGHT + GAPBETWEEN;
214 }
215
216 struct radio { char *text; int id; };
217
218 static void radioline_common(struct ctlpos *cp, char *text, int id,
219                              int nacross, struct radio *buttons, int nbuttons)
220 {
221     RECT r;
222     int group;
223     int i;
224     int j;
225
226     if (text) {
227         r.left = GAPBETWEEN;
228         r.top = cp->ypos;
229         r.right = cp->width;
230         r.bottom = STATICHEIGHT;
231         cp->ypos += r.bottom + GAPWITHIN;
232         doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
233     }
234
235     group = WS_GROUP;
236     i = 0;
237     for (j = 0; j < nbuttons; j++) {
238         char *btext = buttons[j].text;
239         int bid = buttons[j].id;
240
241         if (i == nacross) {
242             cp->ypos += r.bottom + (nacross > 1 ? GAPBETWEEN : GAPWITHIN);
243             i = 0;
244         }
245         r.left = GAPBETWEEN + i * (cp->width + GAPBETWEEN) / nacross;
246         if (j < nbuttons-1)
247             r.right =
248                 (i + 1) * (cp->width + GAPBETWEEN) / nacross - r.left;
249         else
250             r.right = cp->width - r.left;
251         r.top = cp->ypos;
252         r.bottom = RADIOHEIGHT;
253         doctl(cp, r, "BUTTON",
254               BS_NOTIFY | BS_AUTORADIOBUTTON | WS_CHILD |
255               WS_VISIBLE | WS_TABSTOP | group, 0, btext, bid);
256         group = 0;
257         i++;
258     }
259     cp->ypos += r.bottom + GAPBETWEEN;
260 }
261
262 /*
263  * A set of radio buttons on the same line, with a static above
264  * them. `nacross' dictates how many parts the line is divided into
265  * (you might want this not to equal the number of buttons if you
266  * needed to line up some 2s and some 3s to look good in the same
267  * panel).
268  * 
269  * There's a bit of a hack in here to ensure that if nacross
270  * exceeds the actual number of buttons, the rightmost button
271  * really does get all the space right to the edge of the line, so
272  * you can do things like
273  * 
274  * (*) Button1  (*) Button2  (*) ButtonWithReallyLongTitle
275  */
276 void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...)
277 {
278     va_list ap;
279     struct radio *buttons;
280     int i, nbuttons;
281
282     va_start(ap, nacross);
283     nbuttons = 0;
284     while (1) {
285         char *btext = va_arg(ap, char *);
286         int bid;
287         if (!btext)
288             break;
289         bid = va_arg(ap, int);
290         nbuttons++;
291     }
292     va_end(ap);
293     buttons = snewn(nbuttons, struct radio);
294     va_start(ap, nacross);
295     for (i = 0; i < nbuttons; i++) {
296         buttons[i].text = va_arg(ap, char *);
297         buttons[i].id = va_arg(ap, int);
298     }
299     va_end(ap);
300     radioline_common(cp, text, id, nacross, buttons, nbuttons);
301     sfree(buttons);
302 }
303
304 /*
305  * A set of radio buttons on the same line, without a static above
306  * them. Otherwise just like radioline.
307  */
308 void bareradioline(struct ctlpos *cp, int nacross, ...)
309 {
310     va_list ap;
311     struct radio *buttons;
312     int i, nbuttons;
313
314     va_start(ap, nacross);
315     nbuttons = 0;
316     while (1) {
317         char *btext = va_arg(ap, char *);
318         int bid;
319         if (!btext)
320             break;
321         bid = va_arg(ap, int);
322     }
323     va_end(ap);
324     buttons = snewn(nbuttons, struct radio);
325     va_start(ap, nacross);
326     for (i = 0; i < nbuttons; i++) {
327         buttons[i].text = va_arg(ap, char *);
328         buttons[i].id = va_arg(ap, int);
329     }
330     va_end(ap);
331     radioline_common(cp, NULL, 0, nacross, buttons, nbuttons);
332     sfree(buttons);
333 }
334
335 /*
336  * A set of radio buttons on multiple lines, with a static above
337  * them.
338  */
339 void radiobig(struct ctlpos *cp, char *text, int id, ...)
340 {
341     va_list ap;
342     struct radio *buttons;
343     int i, nbuttons;
344
345     va_start(ap, id);
346     nbuttons = 0;
347     while (1) {
348         char *btext = va_arg(ap, char *);
349         int bid;
350         if (!btext)
351             break;
352         bid = va_arg(ap, int);
353     }
354     va_end(ap);
355     buttons = snewn(nbuttons, struct radio);
356     va_start(ap, id);
357     for (i = 0; i < nbuttons; i++) {
358         buttons[i].text = va_arg(ap, char *);
359         buttons[i].id = va_arg(ap, int);
360     }
361     va_end(ap);
362     radioline_common(cp, text, id, 1, buttons, nbuttons);
363     sfree(buttons);
364 }
365
366 /*
367  * A single standalone checkbox.
368  */
369 void checkbox(struct ctlpos *cp, char *text, int id)
370 {
371     RECT r;
372
373     r.left = GAPBETWEEN;
374     r.top = cp->ypos;
375     r.right = cp->width;
376     r.bottom = CHECKBOXHEIGHT;
377     cp->ypos += r.bottom + GAPBETWEEN;
378     doctl(cp, r, "BUTTON",
379           BS_NOTIFY | BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0,
380           text, id);
381 }
382
383 /*
384  * Wrap a piece of text for a static text control. Returns the
385  * wrapped text (a malloc'ed string containing \ns), and also
386  * returns the number of lines required.
387  */
388 char *staticwrap(struct ctlpos *cp, HWND hwnd, char *text, int *lines)
389 {
390     HDC hdc = GetDC(hwnd);
391     int lpx = GetDeviceCaps(hdc, LOGPIXELSX);
392     int width, nlines, j;
393     INT *pwidths, nfit;
394     SIZE size;
395     char *ret, *p, *q;
396     RECT r;
397     HFONT oldfont, newfont;
398
399     ret = snewn(1+strlen(text), char);
400     p = text;
401     q = ret;
402     pwidths = snewn(1+strlen(text), INT);
403
404     /*
405      * Work out the width the text will need to fit in, by doing
406      * the same adjustment that the `statictext' function itself
407      * will perform.
408      */
409     SetMapMode(hdc, MM_TEXT);          /* ensure logical units == pixels */
410     r.left = r.top = r.bottom = 0;
411     r.right = cp->width;
412     MapDialogRect(hwnd, &r);
413     width = r.right;
414
415     nlines = 1;
416
417     /*
418      * We must select the correct font into the HDC before calling
419      * GetTextExtent*, or silly things will happen.
420      */
421     newfont = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0);
422     oldfont = SelectObject(hdc, newfont);
423
424     while (*p) {
425         if (!GetTextExtentExPoint(hdc, p, strlen(p), width,
426                                   &nfit, pwidths, &size) ||
427             (size_t)nfit >= strlen(p)) {
428             /*
429              * Either GetTextExtentExPoint returned failure, or the
430              * whole of the rest of the text fits on this line.
431              * Either way, we stop wrapping, copy the remainder of
432              * the input string unchanged to the output, and leave.
433              */
434             strcpy(q, p);
435             break;
436         }
437
438         /*
439          * Now we search backwards along the string from `nfit',
440          * looking for a space at which to break the line. If we
441          * don't find one at all, that's fine - we'll just break
442          * the line at `nfit'.
443          */
444         for (j = nfit; j > 0; j--) {
445             if (isspace((unsigned char)p[j])) {
446                 nfit = j;
447                 break;
448             }
449         }
450
451         strncpy(q, p, nfit);
452         q[nfit] = '\n';
453         q += nfit+1;
454
455         p += nfit;
456         while (*p && isspace((unsigned char)*p))
457             p++;
458
459         nlines++;
460     }
461
462     SelectObject(hdc, oldfont);
463     ReleaseDC(cp->hwnd, hdc);
464
465     if (lines) *lines = nlines;
466
467     return ret;
468 }
469
470 /*
471  * A single standalone static text control.
472  */
473 void statictext(struct ctlpos *cp, char *text, int lines, int id)
474 {
475     RECT r;
476
477     r.left = GAPBETWEEN;
478     r.top = cp->ypos;
479     r.right = cp->width;
480     r.bottom = STATICHEIGHT * lines;
481     cp->ypos += r.bottom + GAPBETWEEN;
482     doctl(cp, r, "STATIC",
483           WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
484           0, text, id);
485 }
486
487 /*
488  * An owner-drawn static text control for a panel title.
489  */
490 void paneltitle(struct ctlpos *cp, int id)
491 {
492     RECT r;
493
494     r.left = GAPBETWEEN;
495     r.top = cp->ypos;
496     r.right = cp->width;
497     r.bottom = TITLEHEIGHT;
498     cp->ypos += r.bottom + GAPBETWEEN;
499     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_OWNERDRAW,
500           0, NULL, id);
501 }
502
503 /*
504  * A button on the right hand side, with a static to its left.
505  */
506 void staticbtn(struct ctlpos *cp, char *stext, int sid,
507                char *btext, int bid)
508 {
509     const int height = (PUSHBTNHEIGHT > STATICHEIGHT ?
510                         PUSHBTNHEIGHT : STATICHEIGHT);
511     RECT r;
512     int lwid, rwid, rpos;
513
514     rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
515     lwid = rpos - 2 * GAPBETWEEN;
516     rwid = cp->width + GAPBETWEEN - rpos;
517
518     r.left = GAPBETWEEN;
519     r.top = cp->ypos + (height - STATICHEIGHT) / 2;
520     r.right = lwid;
521     r.bottom = STATICHEIGHT;
522     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
523
524     r.left = rpos;
525     r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
526     r.right = rwid;
527     r.bottom = PUSHBTNHEIGHT;
528     doctl(cp, r, "BUTTON",
529           BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
530           0, btext, bid);
531
532     cp->ypos += height + GAPBETWEEN;
533 }
534
535 /*
536  * A simple push button.
537  */
538 void button(struct ctlpos *cp, char *btext, int bid, int defbtn)
539 {
540     RECT r;
541
542     r.left = GAPBETWEEN;
543     r.top = cp->ypos;
544     r.right = cp->width;
545     r.bottom = PUSHBTNHEIGHT;
546
547     /* Q67655: the _dialog box_ must know which button is default
548      * as well as the button itself knowing */
549     if (defbtn && cp->hwnd)
550         SendMessage(cp->hwnd, DM_SETDEFID, bid, 0);
551
552     doctl(cp, r, "BUTTON",
553           BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP |
554           (defbtn ? BS_DEFPUSHBUTTON : 0) | BS_PUSHBUTTON,
555           0, btext, bid);
556
557     cp->ypos += PUSHBTNHEIGHT + GAPBETWEEN;
558 }
559
560 /*
561  * Like staticbtn, but two buttons.
562  */
563 void static2btn(struct ctlpos *cp, char *stext, int sid,
564                 char *btext1, int bid1, char *btext2, int bid2)
565 {
566     const int height = (PUSHBTNHEIGHT > STATICHEIGHT ?
567                         PUSHBTNHEIGHT : STATICHEIGHT);
568     RECT r;
569     int lwid, rwid1, rwid2, rpos1, rpos2;
570
571     rpos1 = GAPBETWEEN + (cp->width + GAPBETWEEN) / 2;
572     rpos2 = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
573     lwid = rpos1 - 2 * GAPBETWEEN;
574     rwid1 = rpos2 - rpos1 - GAPBETWEEN;
575     rwid2 = cp->width + GAPBETWEEN - rpos2;
576
577     r.left = GAPBETWEEN;
578     r.top = cp->ypos + (height - STATICHEIGHT) / 2;
579     r.right = lwid;
580     r.bottom = STATICHEIGHT;
581     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
582
583     r.left = rpos1;
584     r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
585     r.right = rwid1;
586     r.bottom = PUSHBTNHEIGHT;
587     doctl(cp, r, "BUTTON",
588           BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
589           0, btext1, bid1);
590
591     r.left = rpos2;
592     r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
593     r.right = rwid2;
594     r.bottom = PUSHBTNHEIGHT;
595     doctl(cp, r, "BUTTON",
596           BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
597           0, btext2, bid2);
598
599     cp->ypos += height + GAPBETWEEN;
600 }
601
602 /*
603  * An edit control on the right hand side, with a static to its left.
604  */
605 static void staticedit_internal(struct ctlpos *cp, char *stext,
606                                 int sid, int eid, int percentedit,
607                                 int style)
608 {
609     const int height = (EDITHEIGHT > STATICHEIGHT ?
610                         EDITHEIGHT : STATICHEIGHT);
611     RECT r;
612     int lwid, rwid, rpos;
613
614     rpos =
615         GAPBETWEEN + (100 - percentedit) * (cp->width + GAPBETWEEN) / 100;
616     lwid = rpos - 2 * GAPBETWEEN;
617     rwid = cp->width + GAPBETWEEN - rpos;
618
619     r.left = GAPBETWEEN;
620     r.top = cp->ypos + (height - STATICHEIGHT) / 2;
621     r.right = lwid;
622     r.bottom = STATICHEIGHT;
623     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
624
625     r.left = rpos;
626     r.top = cp->ypos + (height - EDITHEIGHT) / 2;
627     r.right = rwid;
628     r.bottom = EDITHEIGHT;
629     doctl(cp, r, "EDIT",
630           WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | style,
631           WS_EX_CLIENTEDGE, "", eid);
632
633     cp->ypos += height + GAPBETWEEN;
634 }
635
636 void staticedit(struct ctlpos *cp, char *stext,
637                 int sid, int eid, int percentedit)
638 {
639     staticedit_internal(cp, stext, sid, eid, percentedit, 0);
640 }
641
642 void staticpassedit(struct ctlpos *cp, char *stext,
643                     int sid, int eid, int percentedit)
644 {
645     staticedit_internal(cp, stext, sid, eid, percentedit, ES_PASSWORD);
646 }
647
648 /*
649  * A drop-down list box on the right hand side, with a static to
650  * its left.
651  */
652 void staticddl(struct ctlpos *cp, char *stext,
653                int sid, int lid, int percentlist)
654 {
655     const int height = (COMBOHEIGHT > STATICHEIGHT ?
656                         COMBOHEIGHT : STATICHEIGHT);
657     RECT r;
658     int lwid, rwid, rpos;
659
660     rpos =
661         GAPBETWEEN + (100 - percentlist) * (cp->width + GAPBETWEEN) / 100;
662     lwid = rpos - 2 * GAPBETWEEN;
663     rwid = cp->width + GAPBETWEEN - rpos;
664
665     r.left = GAPBETWEEN;
666     r.top = cp->ypos + (height - STATICHEIGHT) / 2;
667     r.right = lwid;
668     r.bottom = STATICHEIGHT;
669     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
670
671     r.left = rpos;
672     r.top = cp->ypos + (height - EDITHEIGHT) / 2;
673     r.right = rwid;
674     r.bottom = COMBOHEIGHT*4;
675     doctl(cp, r, "COMBOBOX",
676           WS_CHILD | WS_VISIBLE | WS_TABSTOP |
677           CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
678
679     cp->ypos += height + GAPBETWEEN;
680 }
681
682 /*
683  * A combo box on the right hand side, with a static to its left.
684  */
685 void staticcombo(struct ctlpos *cp, char *stext,
686                  int sid, int lid, int percentlist)
687 {
688     const int height = (COMBOHEIGHT > STATICHEIGHT ?
689                         COMBOHEIGHT : STATICHEIGHT);
690     RECT r;
691     int lwid, rwid, rpos;
692
693     rpos =
694         GAPBETWEEN + (100 - percentlist) * (cp->width + GAPBETWEEN) / 100;
695     lwid = rpos - 2 * GAPBETWEEN;
696     rwid = cp->width + GAPBETWEEN - rpos;
697
698     r.left = GAPBETWEEN;
699     r.top = cp->ypos + (height - STATICHEIGHT) / 2;
700     r.right = lwid;
701     r.bottom = STATICHEIGHT;
702     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
703
704     r.left = rpos;
705     r.top = cp->ypos + (height - EDITHEIGHT) / 2;
706     r.right = rwid;
707     r.bottom = COMBOHEIGHT*10;
708     doctl(cp, r, "COMBOBOX",
709           WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
710           CBS_DROPDOWN | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
711
712     cp->ypos += height + GAPBETWEEN;
713 }
714
715 /*
716  * A static, with a full-width drop-down list box below it.
717  */
718 void staticddlbig(struct ctlpos *cp, char *stext,
719                   int sid, int lid)
720 {
721     RECT r;
722
723     r.left = GAPBETWEEN;
724     r.top = cp->ypos;
725     r.right = cp->width;
726     r.bottom = STATICHEIGHT;
727     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
728     cp->ypos += STATICHEIGHT;
729
730     r.left = GAPBETWEEN;
731     r.top = cp->ypos;
732     r.right = cp->width;
733     r.bottom = COMBOHEIGHT*4;
734     doctl(cp, r, "COMBOBOX",
735           WS_CHILD | WS_VISIBLE | WS_TABSTOP |
736           CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
737     cp->ypos += COMBOHEIGHT + GAPBETWEEN;
738 }
739
740 /*
741  * A big multiline edit control with a static labelling it.
742  */
743 void bigeditctrl(struct ctlpos *cp, char *stext,
744                  int sid, int eid, int lines)
745 {
746     RECT r;
747
748     r.left = GAPBETWEEN;
749     r.top = cp->ypos;
750     r.right = cp->width;
751     r.bottom = STATICHEIGHT;
752     cp->ypos += r.bottom + GAPWITHIN;
753     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
754
755     r.left = GAPBETWEEN;
756     r.top = cp->ypos;
757     r.right = cp->width;
758     r.bottom = EDITHEIGHT + (lines - 1) * STATICHEIGHT;
759     cp->ypos += r.bottom + GAPBETWEEN;
760     doctl(cp, r, "EDIT",
761           WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | ES_MULTILINE,
762           WS_EX_CLIENTEDGE, "", eid);
763 }
764
765 /*
766  * A list box with a static labelling it.
767  */
768 void listbox(struct ctlpos *cp, char *stext,
769              int sid, int lid, int lines, int multi)
770 {
771     RECT r;
772
773     if (stext != NULL) {
774         r.left = GAPBETWEEN;
775         r.top = cp->ypos;
776         r.right = cp->width;
777         r.bottom = STATICHEIGHT;
778         cp->ypos += r.bottom + GAPWITHIN;
779         doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
780     }
781
782     r.left = GAPBETWEEN;
783     r.top = cp->ypos;
784     r.right = cp->width;
785     r.bottom = LISTHEIGHT + (lines - 1) * LISTINCREMENT;
786     cp->ypos += r.bottom + GAPBETWEEN;
787     doctl(cp, r, "LISTBOX",
788           WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
789           LBS_NOTIFY | LBS_HASSTRINGS | LBS_USETABSTOPS |
790           (multi ? LBS_MULTIPLESEL : 0),
791           WS_EX_CLIENTEDGE, "", lid);
792 }
793
794 /*
795  * A tab-control substitute when a real tab control is unavailable.
796  */
797 void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id)
798 {
799     const int height = (COMBOHEIGHT > STATICHEIGHT ?
800                         COMBOHEIGHT : STATICHEIGHT);
801     RECT r;
802     int bigwid, lwid, rwid, rpos;
803     static const int BIGGAP = 15;
804     static const int MEDGAP = 3;
805
806     bigwid = cp->width + 2 * GAPBETWEEN - 2 * BIGGAP;
807     cp->ypos += MEDGAP;
808     rpos = BIGGAP + (bigwid + BIGGAP) / 2;
809     lwid = rpos - 2 * BIGGAP;
810     rwid = bigwid + BIGGAP - rpos;
811
812     r.left = BIGGAP;
813     r.top = cp->ypos + (height - STATICHEIGHT) / 2;
814     r.right = lwid;
815     r.bottom = STATICHEIGHT;
816     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
817
818     r.left = rpos;
819     r.top = cp->ypos + (height - COMBOHEIGHT) / 2;
820     r.right = rwid;
821     r.bottom = COMBOHEIGHT * 10;
822     doctl(cp, r, "COMBOBOX",
823           WS_CHILD | WS_VISIBLE | WS_TABSTOP |
824           CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
825
826     cp->ypos += height + MEDGAP + GAPBETWEEN;
827
828     r.left = GAPBETWEEN;
829     r.top = cp->ypos;
830     r.right = cp->width;
831     r.bottom = 2;
832     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
833           0, "", s2id);
834 }
835
836 /*
837  * A static line, followed by an edit control on the left hand side
838  * and a button on the right.
839  */
840 void editbutton(struct ctlpos *cp, char *stext, int sid,
841                 int eid, char *btext, int bid)
842 {
843     const int height = (EDITHEIGHT > PUSHBTNHEIGHT ?
844                         EDITHEIGHT : PUSHBTNHEIGHT);
845     RECT r;
846     int lwid, rwid, rpos;
847
848     r.left = GAPBETWEEN;
849     r.top = cp->ypos;
850     r.right = cp->width;
851     r.bottom = STATICHEIGHT;
852     cp->ypos += r.bottom + GAPWITHIN;
853     doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
854
855     rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
856     lwid = rpos - 2 * GAPBETWEEN;
857     rwid = cp->width + GAPBETWEEN - rpos;
858
859     r.left = GAPBETWEEN;
860     r.top = cp->ypos + (height - EDITHEIGHT) / 2;
861     r.right = lwid;
862     r.bottom = EDITHEIGHT;
863     doctl(cp, r, "EDIT",
864           WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
865           WS_EX_CLIENTEDGE, "", eid);
866
867     r.left = rpos;
868     r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
869     r.right = rwid;
870     r.bottom = PUSHBTNHEIGHT;
871     doctl(cp, r, "BUTTON",
872           BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
873           0, btext, bid);
874
875     cp->ypos += height + GAPBETWEEN;
876 }
877
878 /*
879  * A special control for manipulating an ordered preference list
880  * (eg. for cipher selection).
881  * XXX: this is a rough hack and could be improved.
882  */
883 void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines,
884                char *stext, int sid, int listid, int upbid, int dnbid)
885 {
886     const static int percents[] = { 5, 75, 20 };
887     RECT r;
888     int xpos, percent = 0, i;
889     int listheight = LISTHEIGHT + (lines - 1) * LISTINCREMENT;
890     const int BTNSHEIGHT = 2*PUSHBTNHEIGHT + GAPBETWEEN;
891     int totalheight, buttonpos;
892
893     /* Squirrel away IDs. */
894     hdl->listid = listid;
895     hdl->upbid  = upbid;
896     hdl->dnbid  = dnbid;
897
898     /* The static label. */
899     if (stext != NULL) {
900         r.left = GAPBETWEEN;
901         r.top = cp->ypos;
902         r.right = cp->width;
903         r.bottom = STATICHEIGHT;
904         cp->ypos += r.bottom + GAPWITHIN;
905         doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
906     }
907
908     if (listheight > BTNSHEIGHT) {
909         totalheight = listheight;
910         buttonpos = (listheight - BTNSHEIGHT) / 2;
911     } else {
912         totalheight = BTNSHEIGHT;
913         buttonpos = 0;
914     }
915
916     for (i=0; i<3; i++) {
917         int left, wid;
918         xpos = (cp->width + GAPBETWEEN) * percent / 100;
919         left = xpos + GAPBETWEEN;
920         percent += percents[i];
921         xpos = (cp->width + GAPBETWEEN) * percent / 100;
922         wid = xpos - left;
923
924         switch (i) {
925           case 1:
926             /* The drag list box. */
927             r.left = left; r.right = wid;
928             r.top = cp->ypos; r.bottom = listheight;
929             {
930                 HWND ctl;
931                 ctl = doctl(cp, r, "LISTBOX",
932                             WS_CHILD | WS_VISIBLE | WS_TABSTOP |
933                             WS_VSCROLL | LBS_HASSTRINGS | LBS_USETABSTOPS,
934                             WS_EX_CLIENTEDGE,
935                             "", listid);
936                 MakeDragList(ctl);
937             }
938             break;
939
940           case 2:
941             /* The "Up" and "Down" buttons. */
942             /* XXX worry about accelerators if we have more than one
943              * prefslist on a panel */
944             r.left = left; r.right = wid;
945             r.top = cp->ypos + buttonpos; r.bottom = PUSHBTNHEIGHT;
946             doctl(cp, r, "BUTTON",
947                   BS_NOTIFY | WS_CHILD | WS_VISIBLE |
948                   WS_TABSTOP | BS_PUSHBUTTON,
949                   0, "&Up", upbid);
950
951             r.left = left; r.right = wid;
952             r.top = cp->ypos + buttonpos + PUSHBTNHEIGHT + GAPBETWEEN;
953             r.bottom = PUSHBTNHEIGHT;
954             doctl(cp, r, "BUTTON",
955                   BS_NOTIFY | WS_CHILD | WS_VISIBLE |
956                   WS_TABSTOP | BS_PUSHBUTTON,
957                   0, "&Down", dnbid);
958
959             break;
960
961         }
962     }
963
964     cp->ypos += totalheight + GAPBETWEEN;
965
966 }
967
968 /*
969  * Helper function for prefslist: move item in list box.
970  */
971 static void pl_moveitem(HWND hwnd, int listid, int src, int dst)
972 {
973     int tlen, val;
974     char *txt;
975     /* Get the item's data. */
976     tlen = SendDlgItemMessage (hwnd, listid, LB_GETTEXTLEN, src, 0);
977     txt = snewn(tlen+1, char);
978     SendDlgItemMessage (hwnd, listid, LB_GETTEXT, src, (LPARAM) txt);
979     val = SendDlgItemMessage (hwnd, listid, LB_GETITEMDATA, src, 0);
980     /* Deselect old location. */
981     SendDlgItemMessage (hwnd, listid, LB_SETSEL, FALSE, src);
982     /* Delete it at the old location. */
983     SendDlgItemMessage (hwnd, listid, LB_DELETESTRING, src, 0);
984     /* Insert it at new location. */
985     SendDlgItemMessage (hwnd, listid, LB_INSERTSTRING, dst,
986                         (LPARAM) txt);
987     SendDlgItemMessage (hwnd, listid, LB_SETITEMDATA, dst,
988                         (LPARAM) val);
989     /* Set selection. */
990     SendDlgItemMessage (hwnd, listid, LB_SETCURSEL, dst, 0);
991     sfree (txt);
992 }
993
994 int pl_itemfrompt(HWND hwnd, POINT cursor, BOOL scroll)
995 {
996     int ret;
997     POINT uppoint, downpoint;
998     int updist, downdist, upitem, downitem, i;
999
1000     /*
1001      * Ghastly hackery to try to figure out not which
1002      * _item_, but which _gap between items_, the user
1003      * is pointing at. We do this by first working out
1004      * which list item is under the cursor, and then
1005      * working out how far the cursor would have to
1006      * move up or down before the answer was different.
1007      * Then we put the insertion point _above_ the
1008      * current item if the upper edge is closer than
1009      * the lower edge, or _below_ it if vice versa.
1010      */
1011     ret = LBItemFromPt(hwnd, cursor, scroll);
1012     if (ret == -1)
1013         return ret;
1014     ret = LBItemFromPt(hwnd, cursor, FALSE);
1015     updist = downdist = 0;
1016     for (i = 1; i < 4096 && (!updist || !downdist); i++) {
1017         uppoint = downpoint = cursor;
1018         uppoint.y -= i;
1019         downpoint.y += i;
1020         upitem = LBItemFromPt(hwnd, uppoint, FALSE);
1021         downitem = LBItemFromPt(hwnd, downpoint, FALSE);
1022         if (!updist && upitem != ret)
1023             updist = i;
1024         if (!downdist && downitem != ret)
1025             downdist = i;
1026     }
1027     if (downdist < updist)
1028         ret++;
1029     return ret;
1030 }
1031
1032 /*
1033  * Handler for prefslist above.
1034  * 
1035  * Return value has bit 0 set if the dialog box procedure needs to
1036  * return TRUE from handling this message; it has bit 1 set if a
1037  * change may have been made in the contents of the list.
1038  */
1039 int handle_prefslist(struct prefslist *hdl,
1040                      int *array, int maxmemb,
1041                      int is_dlmsg, HWND hwnd,
1042                      WPARAM wParam, LPARAM lParam)
1043 {
1044     int i;
1045     int ret = 0;
1046
1047     if (is_dlmsg) {
1048
1049         if ((int)wParam == hdl->listid) {
1050             DRAGLISTINFO *dlm = (DRAGLISTINFO *)lParam;
1051             int dest = 0;              /* initialise to placate gcc */
1052             switch (dlm->uNotification) {
1053               case DL_BEGINDRAG:
1054                 hdl->dummyitem =
1055                     SendDlgItemMessage(hwnd, hdl->listid,
1056                                        LB_ADDSTRING, 0, (LPARAM) "");
1057
1058                 hdl->srcitem = LBItemFromPt(dlm->hWnd, dlm->ptCursor, TRUE);
1059                 hdl->dragging = 0;
1060                 /* XXX hack Q183115 */
1061                 SetWindowLong(hwnd, DWL_MSGRESULT, TRUE);
1062                 ret |= 1; break;
1063               case DL_CANCELDRAG:
1064                 DrawInsert(hwnd, dlm->hWnd, -1);     /* Clear arrow */
1065                 SendDlgItemMessage(hwnd, hdl->listid,
1066                                    LB_DELETESTRING, hdl->dummyitem, 0);
1067                 hdl->dragging = 0;
1068                 ret |= 1; break;
1069               case DL_DRAGGING:
1070                 hdl->dragging = 1;
1071                 dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, TRUE);
1072                 if (dest > hdl->dummyitem) dest = hdl->dummyitem;
1073                 DrawInsert (hwnd, dlm->hWnd, dest);
1074                 if (dest >= 0)
1075                     SetWindowLong(hwnd, DWL_MSGRESULT, DL_MOVECURSOR);
1076                 else
1077                     SetWindowLong(hwnd, DWL_MSGRESULT, DL_STOPCURSOR);
1078                 ret |= 1; break;
1079               case DL_DROPPED:
1080                 if (hdl->dragging) {
1081                     dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, TRUE);
1082                     if (dest > hdl->dummyitem) dest = hdl->dummyitem;
1083                     DrawInsert (hwnd, dlm->hWnd, -1);
1084                 }
1085                 SendDlgItemMessage(hwnd, hdl->listid,
1086                                    LB_DELETESTRING, hdl->dummyitem, 0);
1087                 if (hdl->dragging) {
1088                     hdl->dragging = 0;
1089                     if (dest >= 0) {
1090                         /* Correct for "missing" item. */
1091                         if (dest > hdl->srcitem) dest--;
1092                         pl_moveitem(hwnd, hdl->listid, hdl->srcitem, dest);
1093                     }
1094                     ret |= 2;
1095                 }
1096                 ret |= 1; break;
1097             }
1098         }
1099
1100     } else {
1101
1102         if (((LOWORD(wParam) == hdl->upbid) ||
1103              (LOWORD(wParam) == hdl->dnbid)) &&
1104             ((HIWORD(wParam) == BN_CLICKED) ||
1105              (HIWORD(wParam) == BN_DOUBLECLICKED))) {
1106             /* Move an item up or down the list. */
1107             /* Get the current selection, if any. */
1108             int selection = SendDlgItemMessage (hwnd, hdl->listid, LB_GETCURSEL, 0, 0);
1109             if (selection == LB_ERR) {
1110                 MessageBeep(0);
1111             } else {
1112                 int nitems;
1113                 /* Get the total number of items. */
1114                 nitems = SendDlgItemMessage (hwnd, hdl->listid, LB_GETCOUNT, 0, 0);
1115                 /* Should we do anything? */
1116                 if (LOWORD(wParam) == hdl->upbid && (selection > 0))
1117                     pl_moveitem(hwnd, hdl->listid, selection, selection - 1);
1118                 else if (LOWORD(wParam) == hdl->dnbid && (selection < nitems - 1))
1119                     pl_moveitem(hwnd, hdl->listid, selection, selection + 1);
1120                 ret |= 2;
1121             }
1122
1123         }
1124
1125     }
1126
1127     if (array) {
1128         /* Update array to match the list box. */
1129         for (i=0; i < maxmemb; i++)
1130             array[i] = SendDlgItemMessage (hwnd, hdl->listid, LB_GETITEMDATA,
1131                                            i, 0);
1132     }
1133
1134     return ret;
1135 }
1136
1137 /*
1138  * A progress bar (from Common Controls). We like our progress bars
1139  * to be smooth and unbroken, without those ugly divisions; some
1140  * older compilers may not support that, but that's life.
1141  */
1142 void progressbar(struct ctlpos *cp, int id)
1143 {
1144     RECT r;
1145
1146     r.left = GAPBETWEEN;
1147     r.top = cp->ypos;
1148     r.right = cp->width;
1149     r.bottom = PROGBARHEIGHT;
1150     cp->ypos += r.bottom + GAPBETWEEN;
1151
1152     doctl(cp, r, PROGRESS_CLASS, WS_CHILD | WS_VISIBLE
1153 #ifdef PBS_SMOOTH
1154           | PBS_SMOOTH
1155 #endif
1156           , WS_EX_CLIENTEDGE, "", id);
1157 }
1158
1159 /* ----------------------------------------------------------------------
1160  * Platform-specific side of portable dialog-box mechanism.
1161  */
1162
1163 /*
1164  * This function takes a string, escapes all the ampersands, and
1165  * places a single (unescaped) ampersand in front of the first
1166  * occurrence of the given shortcut character (which may be
1167  * NO_SHORTCUT).
1168  * 
1169  * Return value is a malloc'ed copy of the processed version of the
1170  * string.
1171  */
1172 static char *shortcut_escape(char *text, char shortcut)
1173 {
1174     char *ret;
1175     char *p, *q;
1176
1177     if (!text)
1178         return NULL;                   /* sfree won't choke on this */
1179
1180     ret = snewn(2*strlen(text)+1, char);   /* size potentially doubles! */
1181     shortcut = tolower((unsigned char)shortcut);
1182
1183     p = text;
1184     q = ret;
1185     while (*p) {
1186         if (shortcut != NO_SHORTCUT &&
1187             tolower((unsigned char)*p) == shortcut) {
1188             *q++ = '&';
1189             shortcut = NO_SHORTCUT;    /* stop it happening twice */
1190         } else if (*p == '&') {
1191             *q++ = '&';
1192         }
1193         *q++ = *p++;
1194     }
1195     *q = '\0';
1196     return ret;
1197 }
1198
1199 void winctrl_add_shortcuts(struct dlgparam *dp, struct winctrl *c)
1200 {
1201     int i;
1202     for (i = 0; i < lenof(c->shortcuts); i++)
1203         if (c->shortcuts[i] != NO_SHORTCUT) {
1204             unsigned char s = tolower((unsigned char)c->shortcuts[i]);
1205             assert(!dp->shortcuts[s]);
1206             dp->shortcuts[s] = TRUE;
1207         }
1208 }
1209
1210 void winctrl_rem_shortcuts(struct dlgparam *dp, struct winctrl *c)
1211 {
1212     int i;
1213     for (i = 0; i < lenof(c->shortcuts); i++)
1214         if (c->shortcuts[i] != NO_SHORTCUT) {
1215             unsigned char s = tolower((unsigned char)c->shortcuts[i]);
1216             assert(dp->shortcuts[s]);
1217             dp->shortcuts[s] = FALSE;
1218         }
1219 }
1220
1221 static int winctrl_cmp_byctrl(void *av, void *bv)
1222 {
1223     struct winctrl *a = (struct winctrl *)av;
1224     struct winctrl *b = (struct winctrl *)bv;
1225     if (a->ctrl < b->ctrl)
1226         return -1;
1227     else if (a->ctrl > b->ctrl)
1228         return +1;
1229     else
1230         return 0;
1231 }
1232 static int winctrl_cmp_byid(void *av, void *bv)
1233 {
1234     struct winctrl *a = (struct winctrl *)av;
1235     struct winctrl *b = (struct winctrl *)bv;
1236     if (a->base_id < b->base_id)
1237         return -1;
1238     else if (a->base_id > b->base_id)
1239         return +1;
1240     else
1241         return 0;
1242 }
1243 static int winctrl_cmp_byctrl_find(void *av, void *bv)
1244 {
1245     union control *a = (union control *)av;
1246     struct winctrl *b = (struct winctrl *)bv;
1247     if (a < b->ctrl)
1248         return -1;
1249     else if (a > b->ctrl)
1250         return +1;
1251     else
1252         return 0;
1253 }
1254 static int winctrl_cmp_byid_find(void *av, void *bv)
1255 {
1256     int *a = (int *)av;
1257     struct winctrl *b = (struct winctrl *)bv;
1258     if (*a < b->base_id)
1259         return -1;
1260     else if (*a >= b->base_id + b->num_ids)
1261         return +1;
1262     else
1263         return 0;
1264 }
1265
1266 void winctrl_init(struct winctrls *wc)
1267 {
1268     wc->byctrl = newtree234(winctrl_cmp_byctrl);
1269     wc->byid = newtree234(winctrl_cmp_byid);
1270 }
1271 void winctrl_cleanup(struct winctrls *wc)
1272 {
1273     struct winctrl *c;
1274
1275     while ((c = index234(wc->byid, 0)) != NULL) {
1276         winctrl_remove(wc, c);
1277         sfree(c->data);
1278         sfree(c);
1279     }
1280
1281     freetree234(wc->byctrl);
1282     freetree234(wc->byid);
1283     wc->byctrl = wc->byid = NULL;
1284 }
1285
1286 void winctrl_add(struct winctrls *wc, struct winctrl *c)
1287 {
1288     struct winctrl *ret;
1289     if (c->ctrl) {
1290         ret = add234(wc->byctrl, c);
1291         assert(ret == c);
1292     }
1293     ret = add234(wc->byid, c);
1294     assert(ret == c);
1295 }
1296
1297 void winctrl_remove(struct winctrls *wc, struct winctrl *c)
1298 {
1299     struct winctrl *ret;
1300     ret = del234(wc->byctrl, c);
1301     ret = del234(wc->byid, c);
1302     assert(ret == c);
1303 }
1304
1305 struct winctrl *winctrl_findbyctrl(struct winctrls *wc, union control *ctrl)
1306 {
1307     return find234(wc->byctrl, ctrl, winctrl_cmp_byctrl_find);
1308 }
1309
1310 struct winctrl *winctrl_findbyid(struct winctrls *wc, int id)
1311 {
1312     return find234(wc->byid, &id, winctrl_cmp_byid_find);
1313 }
1314
1315 struct winctrl *winctrl_findbyindex(struct winctrls *wc, int index)
1316 {
1317     return index234(wc->byid, index);
1318 }
1319
1320 void winctrl_layout(struct dlgparam *dp, struct winctrls *wc,
1321                     struct ctlpos *cp, struct controlset *s, int *id)
1322 {
1323     struct ctlpos columns[16];
1324     int ncols, colstart, colspan;
1325
1326     struct ctlpos tabdelays[16];
1327     union control *tabdelayed[16];
1328     int ntabdelays;
1329
1330     struct ctlpos pos;
1331
1332     char shortcuts[MAX_SHORTCUTS_PER_CTRL];
1333     int nshortcuts;
1334     char *escaped;
1335     int i, actual_base_id, base_id, num_ids;
1336     void *data;
1337
1338     base_id = *id;
1339
1340     /* Start a containing box, if we have a boxname. */
1341     if (s->boxname && *s->boxname) {
1342         struct winctrl *c = snew(struct winctrl);
1343         c->ctrl = NULL;
1344         c->base_id = base_id;
1345         c->num_ids = 1;
1346         c->data = NULL;
1347         memset(c->shortcuts, NO_SHORTCUT, lenof(c->shortcuts));
1348         winctrl_add(wc, c);
1349         beginbox(cp, s->boxtitle, base_id);
1350         base_id++;
1351     }
1352
1353     /* Draw a title, if we have one. */
1354     if (!s->boxname && s->boxtitle) {
1355         struct winctrl *c = snew(struct winctrl);
1356         c->ctrl = NULL;
1357         c->base_id = base_id;
1358         c->num_ids = 1;
1359         c->data = dupstr(s->boxtitle);
1360         memset(c->shortcuts, NO_SHORTCUT, lenof(c->shortcuts));
1361         winctrl_add(wc, c);
1362         paneltitle(cp, base_id);
1363         base_id++;
1364     }
1365
1366     /* Initially we have just one column. */
1367     ncols = 1;
1368     columns[0] = *cp;                  /* structure copy */
1369
1370     /* And initially, there are no pending tab-delayed controls. */
1371     ntabdelays = 0;
1372
1373     /* Loop over each control in the controlset. */
1374     for (i = 0; i < s->ncontrols; i++) {
1375         union control *ctrl = s->ctrls[i];
1376
1377         /*
1378          * Generic processing that pertains to all control types.
1379          * At the end of this if statement, we'll have produced
1380          * `ctrl' (a pointer to the control we have to create, or
1381          * think about creating, in this iteration of the loop),
1382          * `pos' (a suitable ctlpos with which to position it), and
1383          * `c' (a winctrl structure to receive details of the
1384          * dialog IDs). Or we'll have done a `continue', if it was
1385          * CTRL_COLUMNS and doesn't require any control creation at
1386          * all.
1387          */
1388         if (ctrl->generic.type == CTRL_COLUMNS) {
1389             assert((ctrl->columns.ncols == 1) ^ (ncols == 1));
1390
1391             if (ncols == 1) {
1392                 /*
1393                  * We're splitting into multiple columns.
1394                  */
1395                 int lpercent, rpercent, lx, rx, i;
1396
1397                 ncols = ctrl->columns.ncols;
1398                 assert(ncols <= lenof(columns));
1399                 for (i = 1; i < ncols; i++)
1400                     columns[i] = columns[0];   /* structure copy */
1401
1402                 lpercent = 0;
1403                 for (i = 0; i < ncols; i++) {
1404                     rpercent = lpercent + ctrl->columns.percentages[i];
1405                     lx = columns[i].xoff + lpercent *
1406                         (columns[i].width + GAPBETWEEN) / 100;
1407                     rx = columns[i].xoff + rpercent *
1408                         (columns[i].width + GAPBETWEEN) / 100;
1409                     columns[i].xoff = lx;
1410                     columns[i].width = rx - lx - GAPBETWEEN;
1411                     lpercent = rpercent;
1412                 }
1413             } else {
1414                 /*
1415                  * We're recombining the various columns into one.
1416                  */
1417                 int maxy = columns[0].ypos;
1418                 int i;
1419                 for (i = 1; i < ncols; i++)
1420                     if (maxy < columns[i].ypos)
1421                         maxy = columns[i].ypos;
1422                 ncols = 1;
1423                 columns[0] = *cp;      /* structure copy */
1424                 columns[0].ypos = maxy;
1425             }
1426
1427             continue;
1428         } else if (ctrl->generic.type == CTRL_TABDELAY) {
1429             int i;
1430
1431             assert(!ctrl->generic.tabdelay);
1432             ctrl = ctrl->tabdelay.ctrl;
1433
1434             for (i = 0; i < ntabdelays; i++)
1435                 if (tabdelayed[i] == ctrl)
1436                     break;
1437             assert(i < ntabdelays);    /* we have to have found it */
1438
1439             pos = tabdelays[i];        /* structure copy */
1440
1441             colstart = colspan = -1;   /* indicate this was tab-delayed */
1442
1443         } else {
1444             /*
1445              * If it wasn't one of those, it's a genuine control;
1446              * so we'll have to compute a position for it now, by
1447              * checking its column span.
1448              */
1449             int col;
1450
1451             colstart = COLUMN_START(ctrl->generic.column);
1452             colspan = COLUMN_SPAN(ctrl->generic.column);
1453
1454             pos = columns[colstart];   /* structure copy */
1455             pos.width = columns[colstart+colspan-1].width +
1456                 (columns[colstart+colspan-1].xoff - columns[colstart].xoff);
1457
1458             for (col = colstart; col < colstart+colspan; col++)
1459                 if (pos.ypos < columns[col].ypos)
1460                     pos.ypos = columns[col].ypos;
1461
1462             /*
1463              * If this control is to be tabdelayed, add it to the
1464              * tabdelay list, and unset pos.hwnd to inhibit actual
1465              * control creation.
1466              */
1467             if (ctrl->generic.tabdelay) {
1468                 assert(ntabdelays < lenof(tabdelays));
1469                 tabdelays[ntabdelays] = pos;   /* structure copy */
1470                 tabdelayed[ntabdelays] = ctrl;
1471                 ntabdelays++;
1472                 pos.hwnd = NULL;
1473             }
1474         }
1475
1476         /* Most controls don't need anything in c->data. */
1477         data = NULL;
1478
1479         /* And they all start off with no shortcuts registered. */
1480         memset(shortcuts, NO_SHORTCUT, lenof(shortcuts));
1481         nshortcuts = 0;
1482
1483         /* Almost all controls start at base_id. */
1484         actual_base_id = base_id;
1485
1486         /*
1487          * Now we're ready to actually create the control, by
1488          * switching on its type.
1489          */
1490         switch (ctrl->generic.type) {
1491           case CTRL_TEXT:
1492             {
1493                 char *wrapped, *escaped;
1494                 int lines;
1495                 num_ids = 1;
1496                 wrapped = staticwrap(&pos, cp->hwnd,
1497                                      ctrl->generic.label, &lines);
1498                 escaped = shortcut_escape(wrapped, NO_SHORTCUT);
1499                 statictext(&pos, escaped, lines, base_id);
1500                 sfree(escaped);
1501                 sfree(wrapped);
1502             }
1503             break;
1504           case CTRL_EDITBOX:
1505             num_ids = 2;               /* static, edit */
1506             escaped = shortcut_escape(ctrl->editbox.label,
1507                                       ctrl->editbox.shortcut);
1508             shortcuts[nshortcuts++] = ctrl->editbox.shortcut;
1509             if (ctrl->editbox.percentwidth == 100) {
1510                 if (ctrl->editbox.has_list)
1511                     combobox(&pos, escaped,
1512                              base_id, base_id+1);
1513                 else
1514                     multiedit(&pos, ctrl->editbox.password, escaped,
1515                               base_id, base_id+1, 100, NULL);
1516             } else {
1517                 if (ctrl->editbox.has_list) {
1518                     staticcombo(&pos, escaped, base_id, base_id+1,
1519                                 ctrl->editbox.percentwidth);
1520                 } else {
1521                     (ctrl->editbox.password ? staticpassedit : staticedit)
1522                         (&pos, escaped, base_id, base_id+1,
1523                          ctrl->editbox.percentwidth);
1524                 }
1525             }
1526             sfree(escaped);
1527             break;
1528           case CTRL_RADIO:
1529             num_ids = ctrl->radio.nbuttons + 1;   /* label as well */
1530             {
1531                 struct radio *buttons;
1532                 int i;
1533
1534                 escaped = shortcut_escape(ctrl->radio.label,
1535                                           ctrl->radio.shortcut);
1536                 shortcuts[nshortcuts++] = ctrl->radio.shortcut;
1537
1538                 buttons = snewn(ctrl->radio.nbuttons, struct radio);
1539
1540                 for (i = 0; i < ctrl->radio.nbuttons; i++) {
1541                     buttons[i].text =
1542                         shortcut_escape(ctrl->radio.buttons[i],
1543                                         (char)(ctrl->radio.shortcuts ?
1544                                                ctrl->radio.shortcuts[i] :
1545                                                NO_SHORTCUT));
1546                     buttons[i].id = base_id + 1 + i;
1547                     if (ctrl->radio.shortcuts) {
1548                         assert(nshortcuts < MAX_SHORTCUTS_PER_CTRL);
1549                         shortcuts[nshortcuts++] = ctrl->radio.shortcuts[i];
1550                     }
1551                 }
1552
1553                 radioline_common(&pos, escaped, base_id,
1554                                  ctrl->radio.ncolumns,
1555                                  buttons, ctrl->radio.nbuttons);
1556
1557                 for (i = 0; i < ctrl->radio.nbuttons; i++) {
1558                     sfree(buttons[i].text);
1559                 }
1560                 sfree(buttons);
1561                 sfree(escaped);
1562             }
1563             break;
1564           case CTRL_CHECKBOX:
1565             num_ids = 1;
1566             escaped = shortcut_escape(ctrl->checkbox.label,
1567                                       ctrl->checkbox.shortcut);
1568             shortcuts[nshortcuts++] = ctrl->checkbox.shortcut;
1569             checkbox(&pos, escaped, base_id);
1570             sfree(escaped);
1571             break;
1572           case CTRL_BUTTON:
1573             escaped = shortcut_escape(ctrl->button.label,
1574                                       ctrl->button.shortcut);
1575             shortcuts[nshortcuts++] = ctrl->button.shortcut;
1576             if (ctrl->button.iscancel)
1577                 actual_base_id = IDCANCEL;
1578             num_ids = 1;
1579             button(&pos, escaped, actual_base_id, ctrl->button.isdefault);
1580             sfree(escaped);
1581             break;
1582           case CTRL_LISTBOX:
1583             num_ids = 2;
1584             escaped = shortcut_escape(ctrl->listbox.label,
1585                                       ctrl->listbox.shortcut);
1586             shortcuts[nshortcuts++] = ctrl->listbox.shortcut;
1587             if (ctrl->listbox.draglist) {
1588                 data = snew(struct prefslist);
1589                 num_ids = 4;
1590                 prefslist(data, &pos, ctrl->listbox.height, escaped,
1591                           base_id, base_id+1, base_id+2, base_id+3);
1592                 shortcuts[nshortcuts++] = 'u';   /* Up */
1593                 shortcuts[nshortcuts++] = 'd';   /* Down */
1594             } else if (ctrl->listbox.height == 0) {
1595                 /* Drop-down list. */
1596                 if (ctrl->listbox.percentwidth == 100) {
1597                     staticddlbig(&pos, escaped,
1598                                  base_id, base_id+1);
1599                 } else {
1600                     staticddl(&pos, escaped, base_id,
1601                               base_id+1, ctrl->listbox.percentwidth);
1602                 }
1603             } else {
1604                 /* Ordinary list. */
1605                 listbox(&pos, escaped, base_id, base_id+1,
1606                         ctrl->listbox.height, ctrl->listbox.multisel);
1607             }
1608             if (ctrl->listbox.ncols) {
1609                 /*
1610                  * This method of getting the box width is a bit of
1611                  * a hack; we'd do better to try to retrieve the
1612                  * actual width in dialog units from doctl() just
1613                  * before MapDialogRect. But that's going to be no
1614                  * fun, and this should be good enough accuracy.
1615                  */
1616                 int width = cp->width * ctrl->listbox.percentwidth;
1617                 int *tabarray;
1618                 int i, percent;
1619
1620                 tabarray = snewn(ctrl->listbox.ncols-1, int);
1621                 percent = 0;
1622                 for (i = 0; i < ctrl->listbox.ncols-1; i++) {
1623                     percent += ctrl->listbox.percentages[i];
1624                     tabarray[i] = width * percent / 10000;
1625                 }
1626                 SendDlgItemMessage(cp->hwnd, base_id+1, LB_SETTABSTOPS,
1627                                    ctrl->listbox.ncols-1, (LPARAM)tabarray);
1628                 sfree(tabarray);
1629             }
1630             sfree(escaped);
1631             break;
1632           case CTRL_FILESELECT:
1633             num_ids = 3;
1634             escaped = shortcut_escape(ctrl->fileselect.label,
1635                                       ctrl->fileselect.shortcut);
1636             shortcuts[nshortcuts++] = ctrl->fileselect.shortcut;
1637             editbutton(&pos, escaped, base_id, base_id+1,
1638                        "Bro&wse...", base_id+2);
1639             shortcuts[nshortcuts++] = 'w';
1640             sfree(escaped);
1641             break;
1642           case CTRL_FONTSELECT:
1643             num_ids = 3;
1644             escaped = shortcut_escape(ctrl->fontselect.label,
1645                                       ctrl->fontselect.shortcut);
1646             shortcuts[nshortcuts++] = ctrl->fontselect.shortcut;
1647             statictext(&pos, escaped, 1, base_id);
1648             staticbtn(&pos, "", base_id+1, "Change...", base_id+2);
1649             sfree(escaped);
1650             data = snew(FontSpec);
1651             break;
1652           default:
1653             assert(!"Can't happen");
1654             num_ids = 0;               /* placate gcc */
1655             break;
1656         }
1657
1658         /*
1659          * Create a `struct winctrl' for this control, and advance
1660          * the dialog ID counter, if it's actually been created
1661          * (and isn't tabdelayed).
1662          */
1663         if (pos.hwnd) {
1664             struct winctrl *c = snew(struct winctrl);
1665
1666             c->ctrl = ctrl;
1667             c->base_id = actual_base_id;
1668             c->num_ids = num_ids;
1669             c->data = data;
1670             memcpy(c->shortcuts, shortcuts, sizeof(shortcuts));
1671             winctrl_add(wc, c);
1672             winctrl_add_shortcuts(dp, c);
1673             if (actual_base_id == base_id)
1674                 base_id += num_ids;
1675         }
1676
1677         if (colstart >= 0) {
1678             /*
1679              * Update the ypos in all columns crossed by this
1680              * control.
1681              */
1682             int i;
1683             for (i = colstart; i < colstart+colspan; i++)
1684                 columns[i].ypos = pos.ypos;
1685         }
1686     }
1687
1688     /*
1689      * We've now finished laying out the controls; so now update
1690      * the ctlpos and control ID that were passed in, terminate
1691      * any containing box, and return.
1692      */
1693     for (i = 0; i < ncols; i++)
1694         if (cp->ypos < columns[i].ypos)
1695             cp->ypos = columns[i].ypos;
1696     *id = base_id;
1697
1698     if (s->boxname && *s->boxname)
1699         endbox(cp);
1700 }
1701
1702 static void winctrl_set_focus(union control *ctrl, struct dlgparam *dp,
1703                               int has_focus)
1704 {
1705     if (has_focus) {
1706         if (dp->focused)
1707             dp->lastfocused = dp->focused;
1708         dp->focused = ctrl;
1709     } else if (!has_focus && dp->focused == ctrl) {
1710         dp->lastfocused = dp->focused;
1711         dp->focused = NULL;
1712     }
1713 }
1714
1715 union control *dlg_last_focused(union control *ctrl, void *dlg)
1716 {
1717     struct dlgparam *dp = (struct dlgparam *)dlg;
1718     return dp->focused == ctrl ? dp->lastfocused : dp->focused;
1719 }
1720
1721 /*
1722  * The dialog-box procedure calls this function to handle Windows
1723  * messages on a control we manage.
1724  */
1725 int winctrl_handle_command(struct dlgparam *dp, UINT msg,
1726                            WPARAM wParam, LPARAM lParam)
1727 {
1728     struct winctrl *c;
1729     union control *ctrl;
1730     int i, id, ret;
1731     static UINT draglistmsg = WM_NULL;
1732
1733     /*
1734      * Filter out pointless window messages. Our interest is in
1735      * WM_COMMAND and the drag list message, and nothing else.
1736      */
1737     if (draglistmsg == WM_NULL)
1738         draglistmsg = RegisterWindowMessage (DRAGLISTMSGSTRING);
1739
1740     if (msg != draglistmsg && msg != WM_COMMAND && msg != WM_DRAWITEM)
1741         return 0;
1742
1743     /*
1744      * Look up the control ID in our data.
1745      */
1746     c = NULL;
1747     for (i = 0; i < dp->nctrltrees; i++) {
1748         c = winctrl_findbyid(dp->controltrees[i], LOWORD(wParam));
1749         if (c)
1750             break;
1751     }
1752     if (!c)
1753         return 0;                      /* we have nothing to do */
1754
1755     if (msg == WM_DRAWITEM) {
1756         /*
1757          * Owner-draw request for a panel title.
1758          */
1759         LPDRAWITEMSTRUCT di = (LPDRAWITEMSTRUCT) lParam;
1760         HDC hdc = di->hDC;
1761         RECT r = di->rcItem;
1762         SIZE s;
1763
1764         SetMapMode(hdc, MM_TEXT);      /* ensure logical units == pixels */
1765
1766         GetTextExtentPoint32(hdc, (char *)c->data,
1767                                  strlen((char *)c->data), &s);
1768         DrawEdge(hdc, &r, EDGE_ETCHED, BF_ADJUST | BF_RECT);
1769         TextOut(hdc,
1770                 r.left + (r.right-r.left-s.cx)/2,
1771                 r.top + (r.bottom-r.top-s.cy)/2,
1772                 (char *)c->data, strlen((char *)c->data));
1773
1774         return TRUE;
1775     }
1776
1777     ctrl = c->ctrl;
1778     id = LOWORD(wParam) - c->base_id;
1779
1780     if (!ctrl || !ctrl->generic.handler)
1781         return 0;                      /* nothing we can do here */
1782
1783     /*
1784      * From here on we do not issue `return' statements until the
1785      * very end of the dialog box: any event handler is entitled to
1786      * ask for a colour selector, so we _must_ always allow control
1787      * to reach the end of this switch statement so that the
1788      * subsequent code can test dp->coloursel_wanted().
1789      */
1790     ret = 0;
1791     dp->coloursel_wanted = FALSE;
1792
1793     /*
1794      * Now switch on the control type and the message.
1795      */
1796     switch (ctrl->generic.type) {
1797       case CTRL_EDITBOX:
1798         if (msg == WM_COMMAND && !ctrl->editbox.has_list &&
1799             (HIWORD(wParam) == EN_SETFOCUS || HIWORD(wParam) == EN_KILLFOCUS))
1800             winctrl_set_focus(ctrl, dp, HIWORD(wParam) == EN_SETFOCUS);
1801         if (msg == WM_COMMAND && ctrl->editbox.has_list &&
1802             (HIWORD(wParam)==CBN_SETFOCUS || HIWORD(wParam)==CBN_KILLFOCUS))
1803             winctrl_set_focus(ctrl, dp, HIWORD(wParam) == CBN_SETFOCUS);
1804
1805         if (msg == WM_COMMAND && !ctrl->editbox.has_list &&
1806             HIWORD(wParam) == EN_CHANGE)
1807             ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
1808         if (msg == WM_COMMAND &&
1809             ctrl->editbox.has_list) {
1810             if (HIWORD(wParam) == CBN_SELCHANGE) {
1811                 int index, len;
1812                 char *text;
1813
1814                 index = SendDlgItemMessage(dp->hwnd, c->base_id+1,
1815                                            CB_GETCURSEL, 0, 0);
1816                 len = SendDlgItemMessage(dp->hwnd, c->base_id+1,
1817                                          CB_GETLBTEXTLEN, index, 0);
1818                 text = snewn(len+1, char);
1819                 SendDlgItemMessage(dp->hwnd, c->base_id+1, CB_GETLBTEXT,
1820                                    index, (LPARAM)text);
1821                 SetDlgItemText(dp->hwnd, c->base_id+1, text);
1822                 sfree(text);
1823                 ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
1824             } else if (HIWORD(wParam) == CBN_EDITCHANGE) {
1825                 ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
1826             } else if (HIWORD(wParam) == CBN_KILLFOCUS) {
1827                 ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH);
1828             }
1829
1830         }
1831         break;
1832       case CTRL_RADIO:
1833         if (msg == WM_COMMAND &&
1834             (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
1835             winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
1836         /*
1837          * We sometimes get spurious BN_CLICKED messages for the
1838          * radio button that is just about to _lose_ selection, if
1839          * we're switching using the arrow keys. Therefore we
1840          * double-check that the button in wParam is actually
1841          * checked before generating an event.
1842          */
1843         if (msg == WM_COMMAND &&
1844             (HIWORD(wParam) == BN_CLICKED ||
1845              HIWORD(wParam) == BN_DOUBLECLICKED) &&
1846             IsDlgButtonChecked(dp->hwnd, LOWORD(wParam))) {
1847             ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
1848         }
1849         break;
1850       case CTRL_CHECKBOX:
1851         if (msg == WM_COMMAND &&
1852             (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
1853             winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
1854         if (msg == WM_COMMAND &&
1855             (HIWORD(wParam) == BN_CLICKED ||
1856              HIWORD(wParam) == BN_DOUBLECLICKED)) {
1857             ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
1858         }
1859         break;
1860       case CTRL_BUTTON:
1861         if (msg == WM_COMMAND &&
1862             (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
1863             winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
1864         if (msg == WM_COMMAND &&
1865             (HIWORD(wParam) == BN_CLICKED ||
1866              HIWORD(wParam) == BN_DOUBLECLICKED)) {
1867             ctrl->generic.handler(ctrl, dp, dp->data, EVENT_ACTION);
1868         }
1869         break;
1870       case CTRL_LISTBOX:
1871         if (msg == WM_COMMAND && ctrl->listbox.height != 0 &&
1872             (HIWORD(wParam)==LBN_SETFOCUS || HIWORD(wParam)==LBN_KILLFOCUS))
1873             winctrl_set_focus(ctrl, dp, HIWORD(wParam) == LBN_SETFOCUS);
1874         if (msg == WM_COMMAND && ctrl->listbox.height == 0 &&
1875             (HIWORD(wParam)==CBN_SETFOCUS || HIWORD(wParam)==CBN_KILLFOCUS))
1876             winctrl_set_focus(ctrl, dp, HIWORD(wParam) == CBN_SETFOCUS);
1877         if (msg == WM_COMMAND && id >= 2 &&
1878             (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
1879             winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
1880         if (ctrl->listbox.draglist) {
1881             int pret;
1882             pret = handle_prefslist(c->data, NULL, 0, (msg != WM_COMMAND),
1883                                     dp->hwnd, wParam, lParam);
1884             if (pret & 2)
1885                 ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
1886             ret = pret & 1;
1887         } else {
1888             if (msg == WM_COMMAND && HIWORD(wParam) == LBN_DBLCLK) {
1889                 SetCapture(dp->hwnd);
1890                 ctrl->generic.handler(ctrl, dp, dp->data, EVENT_ACTION);
1891             } else if (msg == WM_COMMAND && HIWORD(wParam) == LBN_SELCHANGE) {
1892                 ctrl->generic.handler(ctrl, dp, dp->data, EVENT_SELCHANGE);
1893             }
1894         }
1895         break;
1896       case CTRL_FILESELECT:
1897         if (msg == WM_COMMAND && id == 1 &&
1898             (HIWORD(wParam) == EN_SETFOCUS || HIWORD(wParam) == EN_KILLFOCUS))
1899             winctrl_set_focus(ctrl, dp, HIWORD(wParam) == EN_SETFOCUS);
1900         if (msg == WM_COMMAND && id == 2 &&
1901             (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
1902             winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
1903         if (msg == WM_COMMAND && id == 1 && HIWORD(wParam) == EN_CHANGE)
1904             ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
1905         if (id == 2 &&
1906             (msg == WM_COMMAND &&
1907              (HIWORD(wParam) == BN_CLICKED ||
1908               HIWORD(wParam) == BN_DOUBLECLICKED))) {
1909             OPENFILENAME of;
1910             char filename[FILENAME_MAX];
1911             int ret;
1912
1913             memset(&of, 0, sizeof(of));
1914 #ifdef OPENFILENAME_SIZE_VERSION_400
1915             of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
1916 #else
1917             of.lStructSize = sizeof(of);
1918 #endif
1919             of.hwndOwner = dp->hwnd;
1920             if (ctrl->fileselect.filter)
1921                 of.lpstrFilter = ctrl->fileselect.filter;
1922             else
1923                 of.lpstrFilter = "All Files (*.*)\0*\0\0\0";
1924             of.lpstrCustomFilter = NULL;
1925             of.nFilterIndex = 1;
1926             of.lpstrFile = filename;
1927             GetDlgItemText(dp->hwnd, c->base_id+1, filename, lenof(filename));
1928             filename[lenof(filename)-1] = '\0';
1929             of.nMaxFile = lenof(filename);
1930             of.lpstrFileTitle = NULL;
1931             of.lpstrInitialDir = NULL;
1932             of.lpstrTitle = ctrl->fileselect.title;
1933             of.Flags = 0;
1934             if (ctrl->fileselect.for_writing)
1935                 ret = GetSaveFileName(&of);
1936             else
1937                 ret = GetOpenFileName(&of);
1938             if (ret) {
1939                 SetDlgItemText(dp->hwnd, c->base_id + 1, filename);
1940                 ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
1941             }
1942         }
1943         break;
1944       case CTRL_FONTSELECT:
1945         if (msg == WM_COMMAND && id == 2 &&
1946             (HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
1947             winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
1948         if (id == 2 &&
1949             (msg == WM_COMMAND &&
1950              (HIWORD(wParam) == BN_CLICKED ||
1951               HIWORD(wParam) == BN_DOUBLECLICKED))) {
1952             CHOOSEFONT cf;
1953             LOGFONT lf;
1954             HDC hdc;
1955             FontSpec fs = *(FontSpec *)c->data;
1956             
1957             hdc = GetDC(0);
1958             lf.lfHeight = -MulDiv(fs.height,
1959                                   GetDeviceCaps(hdc, LOGPIXELSY), 72);
1960             ReleaseDC(0, hdc);
1961             lf.lfWidth = lf.lfEscapement = lf.lfOrientation = 0;
1962             lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = 0;
1963             lf.lfWeight = (fs.isbold ? FW_BOLD : 0);
1964             lf.lfCharSet = fs.charset;
1965             lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
1966             lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
1967             lf.lfQuality = DEFAULT_QUALITY;
1968             lf.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
1969             strncpy(lf.lfFaceName, fs.name,
1970                     sizeof(lf.lfFaceName) - 1);
1971             lf.lfFaceName[sizeof(lf.lfFaceName) - 1] = '\0';
1972
1973             cf.lStructSize = sizeof(cf);
1974             cf.hwndOwner = dp->hwnd;
1975             cf.lpLogFont = &lf;
1976             cf.Flags = CF_FIXEDPITCHONLY | CF_FORCEFONTEXIST |
1977                 CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
1978
1979             if (ChooseFont(&cf)) {
1980                 strncpy(fs.name, lf.lfFaceName,
1981                         sizeof(fs.name) - 1);
1982                 fs.name[sizeof(fs.name) - 1] = '\0';
1983                 fs.isbold = (lf.lfWeight == FW_BOLD);
1984                 fs.charset = lf.lfCharSet;
1985                 fs.height = cf.iPointSize / 10;
1986                 dlg_fontsel_set(ctrl, dp, fs);
1987                 ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
1988             }
1989         }
1990         break;
1991     }
1992
1993     /*
1994      * If the above event handler has asked for a colour selector,
1995      * now is the time to generate one.
1996      */
1997     if (dp->coloursel_wanted) {
1998         static CHOOSECOLOR cc;
1999         static DWORD custom[16] = { 0 };    /* zero initialisers */
2000         cc.lStructSize = sizeof(cc);
2001         cc.hwndOwner = dp->hwnd;
2002         cc.hInstance = (HWND) hinst;
2003         cc.lpCustColors = custom;
2004         cc.rgbResult = RGB(dp->coloursel_result.r,
2005                            dp->coloursel_result.g,
2006                            dp->coloursel_result.b);
2007         cc.Flags = CC_FULLOPEN | CC_RGBINIT;
2008         if (ChooseColor(&cc)) {
2009             dp->coloursel_result.r =
2010                 (unsigned char) (cc.rgbResult & 0xFF);
2011             dp->coloursel_result.g =
2012                 (unsigned char) (cc.rgbResult >> 8) & 0xFF;
2013             dp->coloursel_result.b =
2014                 (unsigned char) (cc.rgbResult >> 16) & 0xFF;
2015             dp->coloursel_result.ok = TRUE;
2016         } else
2017             dp->coloursel_result.ok = FALSE;
2018         ctrl->generic.handler(ctrl, dp, dp->data, EVENT_CALLBACK);
2019     }
2020
2021     return ret;
2022 }
2023
2024 /*
2025  * This function can be called to produce context help on a
2026  * control. Returns TRUE if it has actually launched WinHelp.
2027  */
2028 int winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id)
2029 {
2030     int i;
2031     struct winctrl *c;
2032     char *cmd;
2033
2034     /*
2035      * Look up the control ID in our data.
2036      */
2037     c = NULL;
2038     for (i = 0; i < dp->nctrltrees; i++) {
2039         c = winctrl_findbyid(dp->controltrees[i], id);
2040         if (c)
2041             break;
2042     }
2043     if (!c)
2044         return 0;                      /* we have nothing to do */
2045
2046     /*
2047      * This is the Windows front end, so we're allowed to assume
2048      * `helpctx.p' is a context string.
2049      */
2050     if (!c->ctrl || !c->ctrl->generic.helpctx.p)
2051         return 0;                      /* no help available for this ctrl */
2052
2053     cmd = dupprintf("JI(`',`%s')", c->ctrl->generic.helpctx.p);
2054     WinHelp(hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
2055     sfree(cmd);
2056     return 1;
2057 }
2058
2059 /*
2060  * Now the various functions that the platform-independent
2061  * mechanism can call to access the dialog box entries.
2062  */
2063
2064 static struct winctrl *dlg_findbyctrl(struct dlgparam *dp, union control *ctrl)
2065 {
2066     int i;
2067
2068     for (i = 0; i < dp->nctrltrees; i++) {
2069         struct winctrl *c = winctrl_findbyctrl(dp->controltrees[i], ctrl);
2070         if (c)
2071             return c;
2072     }
2073     return NULL;
2074 }
2075
2076 void dlg_radiobutton_set(union control *ctrl, void *dlg, int whichbutton)
2077 {
2078     struct dlgparam *dp = (struct dlgparam *)dlg;
2079     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2080     assert(c && c->ctrl->generic.type == CTRL_RADIO);
2081     CheckRadioButton(dp->hwnd,
2082                      c->base_id + 1,
2083                      c->base_id + c->ctrl->radio.nbuttons,
2084                      c->base_id + 1 + whichbutton);
2085 }
2086
2087 int dlg_radiobutton_get(union control *ctrl, void *dlg)
2088 {
2089     struct dlgparam *dp = (struct dlgparam *)dlg;
2090     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2091     int i;
2092     assert(c && c->ctrl->generic.type == CTRL_RADIO);
2093     for (i = 0; i < c->ctrl->radio.nbuttons; i++)
2094         if (IsDlgButtonChecked(dp->hwnd, c->base_id + 1 + i))
2095             return i;
2096     assert(!"No radio button was checked?!");
2097     return 0;
2098 }
2099
2100 void dlg_checkbox_set(union control *ctrl, void *dlg, int checked)
2101 {
2102     struct dlgparam *dp = (struct dlgparam *)dlg;
2103     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2104     assert(c && c->ctrl->generic.type == CTRL_CHECKBOX);
2105     CheckDlgButton(dp->hwnd, c->base_id, (checked != 0));
2106 }
2107
2108 int dlg_checkbox_get(union control *ctrl, void *dlg)
2109 {
2110     struct dlgparam *dp = (struct dlgparam *)dlg;
2111     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2112     assert(c && c->ctrl->generic.type == CTRL_CHECKBOX);
2113     return 0 != IsDlgButtonChecked(dp->hwnd, c->base_id);
2114 }
2115
2116 void dlg_editbox_set(union control *ctrl, void *dlg, char const *text)
2117 {
2118     struct dlgparam *dp = (struct dlgparam *)dlg;
2119     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2120     assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
2121     SetDlgItemText(dp->hwnd, c->base_id+1, text);
2122 }
2123
2124 void dlg_editbox_get(union control *ctrl, void *dlg, char *buffer, int length)
2125 {
2126     struct dlgparam *dp = (struct dlgparam *)dlg;
2127     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2128     assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
2129     GetDlgItemText(dp->hwnd, c->base_id+1, buffer, length);
2130     buffer[length-1] = '\0';
2131 }
2132
2133 /* The `listbox' functions can also apply to combo boxes. */
2134 void dlg_listbox_clear(union control *ctrl, void *dlg)
2135 {
2136     struct dlgparam *dp = (struct dlgparam *)dlg;
2137     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2138     int msg;
2139     assert(c &&
2140            (c->ctrl->generic.type == CTRL_LISTBOX ||
2141             (c->ctrl->generic.type == CTRL_EDITBOX &&
2142              c->ctrl->editbox.has_list)));
2143     msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
2144            LB_RESETCONTENT : CB_RESETCONTENT);
2145     SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, 0);
2146 }
2147
2148 void dlg_listbox_del(union control *ctrl, void *dlg, int index)
2149 {
2150     struct dlgparam *dp = (struct dlgparam *)dlg;
2151     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2152     int msg;
2153     assert(c &&
2154            (c->ctrl->generic.type == CTRL_LISTBOX ||
2155             (c->ctrl->generic.type == CTRL_EDITBOX &&
2156              c->ctrl->editbox.has_list)));
2157     msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
2158            LB_DELETESTRING : CB_DELETESTRING);
2159     SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0);
2160 }
2161
2162 void dlg_listbox_add(union control *ctrl, void *dlg, char const *text)
2163 {
2164     struct dlgparam *dp = (struct dlgparam *)dlg;
2165     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2166     int msg;
2167     assert(c &&
2168            (c->ctrl->generic.type == CTRL_LISTBOX ||
2169             (c->ctrl->generic.type == CTRL_EDITBOX &&
2170              c->ctrl->editbox.has_list)));
2171     msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
2172            LB_ADDSTRING : CB_ADDSTRING);
2173     SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, (LPARAM)text);
2174 }
2175
2176 /*
2177  * Each listbox entry may have a numeric id associated with it.
2178  * Note that some front ends only permit a string to be stored at
2179  * each position, which means that _if_ you put two identical
2180  * strings in any listbox then you MUST not assign them different
2181  * IDs and expect to get meaningful results back.
2182  */
2183 void dlg_listbox_addwithid(union control *ctrl, void *dlg,
2184                            char const *text, int id)
2185 {
2186     struct dlgparam *dp = (struct dlgparam *)dlg;
2187     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2188     int msg, msg2, index;
2189     assert(c &&
2190            (c->ctrl->generic.type == CTRL_LISTBOX ||
2191             (c->ctrl->generic.type == CTRL_EDITBOX &&
2192              c->ctrl->editbox.has_list)));
2193     msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
2194            LB_ADDSTRING : CB_ADDSTRING);
2195     msg2 = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
2196            LB_SETITEMDATA : CB_SETITEMDATA);
2197     index = SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, (LPARAM)text);
2198     SendDlgItemMessage(dp->hwnd, c->base_id+1, msg2, index, (LPARAM)id);
2199 }
2200
2201 int dlg_listbox_getid(union control *ctrl, void *dlg, int index)
2202 {
2203     struct dlgparam *dp = (struct dlgparam *)dlg;
2204     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2205     int msg;
2206     assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
2207     msg = (c->ctrl->listbox.height != 0 ? LB_GETITEMDATA : CB_GETITEMDATA);
2208     return
2209         SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0);
2210 }
2211
2212 /* dlg_listbox_index returns <0 if no single element is selected. */
2213 int dlg_listbox_index(union control *ctrl, void *dlg)
2214 {
2215     struct dlgparam *dp = (struct dlgparam *)dlg;
2216     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2217     int msg, ret;
2218     assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
2219            !c->ctrl->listbox.multisel);
2220     msg = (c->ctrl->listbox.height != 0 ? LB_GETCURSEL : CB_GETCURSEL);
2221     ret = SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, 0);
2222     if (ret == LB_ERR)
2223         return -1;
2224     else
2225         return ret;
2226 }
2227
2228 int dlg_listbox_issel(union control *ctrl, void *dlg, int index)
2229 {
2230     struct dlgparam *dp = (struct dlgparam *)dlg;
2231     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2232     assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
2233            c->ctrl->listbox.multisel &&
2234            c->ctrl->listbox.height != 0);
2235     return
2236         SendDlgItemMessage(dp->hwnd, c->base_id+1, LB_GETSEL, index, 0);
2237 }
2238
2239 void dlg_listbox_select(union control *ctrl, void *dlg, int index)
2240 {
2241     struct dlgparam *dp = (struct dlgparam *)dlg;
2242     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2243     int msg;
2244     assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
2245            !c->ctrl->listbox.multisel);
2246     msg = (c->ctrl->listbox.height != 0 ? LB_SETCURSEL : CB_SETCURSEL);
2247     SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0);
2248 }
2249
2250 void dlg_text_set(union control *ctrl, void *dlg, char const *text)
2251 {
2252     struct dlgparam *dp = (struct dlgparam *)dlg;
2253     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2254     assert(c && c->ctrl->generic.type == CTRL_TEXT);
2255     SetDlgItemText(dp->hwnd, c->base_id, text);
2256 }
2257
2258 void dlg_filesel_set(union control *ctrl, void *dlg, Filename fn)
2259 {
2260     struct dlgparam *dp = (struct dlgparam *)dlg;
2261     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2262     assert(c && c->ctrl->generic.type == CTRL_FILESELECT);
2263     SetDlgItemText(dp->hwnd, c->base_id+1, fn.path);
2264 }
2265
2266 void dlg_filesel_get(union control *ctrl, void *dlg, Filename *fn)
2267 {
2268     struct dlgparam *dp = (struct dlgparam *)dlg;
2269     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2270     assert(c && c->ctrl->generic.type == CTRL_FILESELECT);
2271     GetDlgItemText(dp->hwnd, c->base_id+1, fn->path, lenof(fn->path));
2272     fn->path[lenof(fn->path)-1] = '\0';
2273 }
2274
2275 void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec fs)
2276 {
2277     char *buf, *boldstr;
2278     struct dlgparam *dp = (struct dlgparam *)dlg;
2279     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2280     assert(c && c->ctrl->generic.type == CTRL_FONTSELECT);
2281
2282     *(FontSpec *)c->data = fs;         /* structure copy */
2283
2284     boldstr = (fs.isbold ? "bold, " : "");
2285     if (fs.height == 0)
2286         buf = dupprintf("Font: %s, %sdefault height", fs.name, boldstr);
2287     else
2288         buf = dupprintf("Font: %s, %s%d-point", fs.name, boldstr,
2289                         (fs.height < 0 ? -fs.height : fs.height));
2290     SetDlgItemText(dp->hwnd, c->base_id+1, buf);
2291     sfree(buf);
2292 }
2293
2294 void dlg_fontsel_get(union control *ctrl, void *dlg, FontSpec *fs)
2295 {
2296     struct dlgparam *dp = (struct dlgparam *)dlg;
2297     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2298     assert(c && c->ctrl->generic.type == CTRL_FONTSELECT);
2299     *fs = *(FontSpec *)c->data;        /* structure copy */
2300 }
2301
2302 /*
2303  * Bracketing a large set of updates in these two functions will
2304  * cause the front end (if possible) to delay updating the screen
2305  * until it's all complete, thus avoiding flicker.
2306  */
2307 void dlg_update_start(union control *ctrl, void *dlg)
2308 {
2309     struct dlgparam *dp = (struct dlgparam *)dlg;
2310     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2311     if (c && c->ctrl->generic.type == CTRL_LISTBOX) {
2312         SendDlgItemMessage(dp->hwnd, c->base_id+1, WM_SETREDRAW, FALSE, 0);
2313     }
2314 }
2315
2316 void dlg_update_done(union control *ctrl, void *dlg)
2317 {
2318     struct dlgparam *dp = (struct dlgparam *)dlg;
2319     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2320     if (c && c->ctrl->generic.type == CTRL_LISTBOX) {
2321         HWND hw = GetDlgItem(dp->hwnd, c->base_id+1);
2322         SendMessage(hw, WM_SETREDRAW, TRUE, 0);
2323         InvalidateRect(hw, NULL, TRUE);
2324     }
2325 }
2326
2327 void dlg_set_focus(union control *ctrl, void *dlg)
2328 {
2329     struct dlgparam *dp = (struct dlgparam *)dlg;
2330     struct winctrl *c = dlg_findbyctrl(dp, ctrl);
2331     int id;
2332     HWND ctl;
2333     switch (ctrl->generic.type) {
2334       case CTRL_EDITBOX: id = c->base_id + 1; break;
2335       case CTRL_RADIO:
2336         for (id = c->base_id + ctrl->radio.nbuttons; id > 1; id--)
2337             if (IsDlgButtonChecked(dp->hwnd, id))
2338                 break;
2339         /*
2340          * In the theoretically-unlikely case that no button was
2341          * selected, id should come out of this as 1, which is a
2342          * reasonable enough choice.
2343          */
2344         break;
2345       case CTRL_CHECKBOX: id = c->base_id; break;
2346       case CTRL_BUTTON: id = c->base_id; break;
2347       case CTRL_LISTBOX: id = c->base_id + 1; break;
2348       case CTRL_FILESELECT: id = c->base_id + 1; break;
2349       case CTRL_FONTSELECT: id = c->base_id + 2; break;
2350       default: id = c->base_id; break;
2351     }
2352     ctl = GetDlgItem(dp->hwnd, id);
2353     SetFocus(ctl);
2354 }
2355
2356 /*
2357  * During event processing, you might well want to give an error
2358  * indication to the user. dlg_beep() is a quick and easy generic
2359  * error; dlg_error() puts up a message-box or equivalent.
2360  */
2361 void dlg_beep(void *dlg)
2362 {
2363     /* struct dlgparam *dp = (struct dlgparam *)dlg; */
2364     MessageBeep(0);
2365 }
2366
2367 void dlg_error_msg(void *dlg, char *msg)
2368 {
2369     struct dlgparam *dp = (struct dlgparam *)dlg;
2370     MessageBox(dp->hwnd, msg,
2371                dp->errtitle ? dp->errtitle : NULL,
2372                MB_OK | MB_ICONERROR);
2373 }
2374
2375 /*
2376  * This function signals to the front end that the dialog's
2377  * processing is completed, and passes an integer value (typically
2378  * a success status).
2379  */
2380 void dlg_end(void *dlg, int value)
2381 {
2382     struct dlgparam *dp = (struct dlgparam *)dlg;
2383     dp->ended = TRUE;
2384     dp->endresult = value;
2385 }
2386
2387 void dlg_refresh(union control *ctrl, void *dlg)
2388 {
2389     struct dlgparam *dp = (struct dlgparam *)dlg;
2390     int i, j;
2391     struct winctrl *c;
2392
2393     if (!ctrl) {
2394         /*
2395          * Send EVENT_REFRESH to absolutely everything.
2396          */
2397         for (j = 0; j < dp->nctrltrees; j++) {
2398             for (i = 0;
2399                  (c = winctrl_findbyindex(dp->controltrees[j], i)) != NULL;
2400                  i++) {
2401                 if (c->ctrl && c->ctrl->generic.handler != NULL)
2402                     c->ctrl->generic.handler(c->ctrl, dp,
2403                                              dp->data, EVENT_REFRESH);
2404             }
2405         }
2406     } else {
2407         /*
2408          * Send EVENT_REFRESH to a specific control.
2409          */
2410         if (ctrl->generic.handler != NULL)
2411             ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH);
2412     }
2413 }
2414
2415 void dlg_coloursel_start(union control *ctrl, void *dlg, int r, int g, int b)
2416 {
2417     struct dlgparam *dp = (struct dlgparam *)dlg;
2418     dp->coloursel_wanted = TRUE;
2419     dp->coloursel_result.r = r;
2420     dp->coloursel_result.g = g;
2421     dp->coloursel_result.b = b;
2422 }
2423
2424 int dlg_coloursel_results(union control *ctrl, void *dlg,
2425                           int *r, int *g, int *b)
2426 {
2427     struct dlgparam *dp = (struct dlgparam *)dlg;
2428     if (dp->coloursel_result.ok) {
2429         *r = dp->coloursel_result.r;
2430         *g = dp->coloursel_result.g;
2431         *b = dp->coloursel_result.b;
2432         return 1;
2433     } else
2434         return 0;
2435 }
2436
2437 struct perctrl_privdata {
2438     union control *ctrl;
2439     void *data;
2440     int needs_free;
2441 };
2442
2443 static int perctrl_privdata_cmp(void *av, void *bv)
2444 {
2445     struct perctrl_privdata *a = (struct perctrl_privdata *)av;
2446     struct perctrl_privdata *b = (struct perctrl_privdata *)bv;
2447     if (a->ctrl < b->ctrl)
2448         return -1;
2449     else if (a->ctrl > b->ctrl)
2450         return +1;
2451     return 0;
2452 }
2453
2454 void dp_init(struct dlgparam *dp)
2455 {
2456     dp->nctrltrees = 0;
2457     dp->data = NULL;
2458     dp->ended = FALSE;
2459     dp->focused = dp->lastfocused = NULL;
2460     memset(dp->shortcuts, 0, sizeof(dp->shortcuts));
2461     dp->hwnd = NULL;
2462     dp->wintitle = dp->errtitle = NULL;
2463     dp->privdata = newtree234(perctrl_privdata_cmp);
2464 }
2465
2466 void dp_add_tree(struct dlgparam *dp, struct winctrls *wc)
2467 {
2468     assert(dp->nctrltrees < lenof(dp->controltrees));
2469     dp->controltrees[dp->nctrltrees++] = wc;
2470 }
2471
2472 void dp_cleanup(struct dlgparam *dp)
2473 {
2474     struct perctrl_privdata *p;
2475
2476     if (dp->privdata) {
2477         while ( (p = index234(dp->privdata, 0)) != NULL ) {
2478             del234(dp->privdata, p);
2479             if (p->needs_free)
2480                 sfree(p->data);
2481             sfree(p);
2482         }
2483         freetree234(dp->privdata);
2484         dp->privdata = NULL;
2485     }
2486     sfree(dp->wintitle);
2487     sfree(dp->errtitle);
2488 }
2489
2490 void *dlg_get_privdata(union control *ctrl, void *dlg)
2491 {
2492     struct dlgparam *dp = (struct dlgparam *)dlg;
2493     struct perctrl_privdata tmp, *p;
2494     tmp.ctrl = ctrl;
2495     p = find234(dp->privdata, &tmp, NULL);
2496     if (p)
2497         return p->data;
2498     else
2499         return NULL;
2500 }
2501
2502 void dlg_set_privdata(union control *ctrl, void *dlg, void *ptr)
2503 {
2504     struct dlgparam *dp = (struct dlgparam *)dlg;
2505     struct perctrl_privdata tmp, *p;
2506     tmp.ctrl = ctrl;
2507     p = find234(dp->privdata, &tmp, NULL);
2508     if (!p) {
2509         p = snew(struct perctrl_privdata);
2510         p->ctrl = ctrl;
2511         p->needs_free = FALSE;
2512         add234(dp->privdata, p);
2513     }
2514     p->data = ptr;
2515 }
2516
2517 void *dlg_alloc_privdata(union control *ctrl, void *dlg, size_t size)
2518 {
2519     struct dlgparam *dp = (struct dlgparam *)dlg;
2520     struct perctrl_privdata tmp, *p;
2521     tmp.ctrl = ctrl;
2522     p = find234(dp->privdata, &tmp, NULL);
2523     if (!p) {
2524         p = snew(struct perctrl_privdata);
2525         p->ctrl = ctrl;
2526         p->needs_free = FALSE;
2527         add234(dp->privdata, p);
2528     }
2529     assert(!p->needs_free);
2530     p->needs_free = TRUE;
2531     /*
2532      * This is an internal allocation routine, so it's allowed to
2533      * use smalloc directly.
2534      */
2535     p->data = smalloc(size);
2536     return p->data;
2537 }