Remove all the "assert(len>0)" which forbade zero-length writes across the
[sgt/putty] / winctrls.c
CommitLineData
8c3cd914 1/*
2 * winctrls.c: routines to self-manage the controls in a dialog
3 * box.
4 */
5
fe8abbf4 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
8c3cd914 16#include <windows.h>
6e522441 17#include <commctrl.h>
fe8abbf4 18#include <assert.h>
d1582b2e 19#include <ctype.h>
8c3cd914 20
21#include "winstuff.h"
fe8abbf4 22#include "misc.h"
23#include "dialog.h"
ca20bfcf 24#include "puttymem.h"
25
26#include "putty.h"
8c3cd914 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
fe8abbf4 34#define TITLEHEIGHT 12
8c3cd914 35#define CHECKBOXHEIGHT 8
36#define RADIOHEIGHT 8
37#define EDITHEIGHT 12
fe8abbf4 38#define LISTHEIGHT 11
39#define LISTINCREMENT 8
8c3cd914 40#define COMBOHEIGHT 12
41#define PUSHBTNHEIGHT 14
6e522441 42#define PROGBARHEIGHT 14
8c3cd914 43
44void ctlposinit(struct ctlpos *cp, HWND hwnd,
32874aea 45 int leftborder, int rightborder, int topborder)
46{
8c3cd914 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;
32874aea 57 cp->width = (r.right * 4) / (r2.right) - 2 * GAPBETWEEN;
8c3cd914 58 cp->xoff = leftborder;
59 cp->width -= leftborder + rightborder;
60}
61
ca20bfcf 62HWND doctl(struct ctlpos *cp, RECT r,
32874aea 63 char *wclass, int wstyle, int exstyle, char *wtext, int wid)
64{
8c3cd914 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
fe8abbf4 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 }
d1582b2e 98 } else
99 ctl = NULL;
ca20bfcf 100 return ctl;
8c3cd914 101}
102
103/*
104 * A title bar across the top of a sub-dialog.
105 */
32874aea 106void bartitle(struct ctlpos *cp, char *name, int id)
107{
8c3cd914 108 RECT r;
109
32874aea 110 r.left = GAPBETWEEN;
111 r.right = cp->width;
112 r.top = cp->ypos;
113 r.bottom = STATICHEIGHT;
8c3cd914 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 */
32874aea 121void beginbox(struct ctlpos *cp, char *name, int idbox)
122{
8c3cd914 123 cp->boxystart = cp->ypos;
3ac9cd9f 124 if (!name)
32874aea 125 cp->boxystart -= STATICHEIGHT / 2;
8c3cd914 126 if (name)
32874aea 127 cp->ypos += STATICHEIGHT;
8c3cd914 128 cp->ypos += GAPYBOX;
32874aea 129 cp->width -= 2 * GAPXBOX;
8c3cd914 130 cp->xoff += GAPXBOX;
131 cp->boxid = idbox;
8c3cd914 132 cp->boxtext = name;
133}
134
135/*
136 * End a grouping box.
137 */
32874aea 138void endbox(struct ctlpos *cp)
139{
8c3cd914 140 RECT r;
141 cp->xoff -= GAPXBOX;
32874aea 142 cp->width += 2 * GAPXBOX;
8c3cd914 143 cp->ypos += GAPYBOX - GAPBETWEEN;
32874aea 144 r.left = GAPBETWEEN;
145 r.right = cp->width;
146 r.top = cp->boxystart;
147 r.bottom = cp->ypos - cp->boxystart;
3ac9cd9f 148 doctl(cp, r, "BUTTON", BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 0,
32874aea 149 cp->boxtext ? cp->boxtext : "", cp->boxid);
8c3cd914 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 */
fe8abbf4 157void multiedit(struct ctlpos *cp, int password, ...)
32874aea 158{
8c3cd914 159 RECT r;
160 va_list ap;
161 int percent, xpos;
162
163 percent = xpos = 0;
fe8abbf4 164 va_start(ap, password);
8c3cd914 165 while (1) {
32874aea 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",
fe8abbf4 186 WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL |
187 (password ? ES_PASSWORD : 0),
32874aea 188 WS_EX_CLIENTEDGE, "", editid);
8c3cd914 189 }
190 va_end(ap);
875b193f 191 cp->ypos += STATICHEIGHT + GAPWITHIN + EDITHEIGHT + GAPBETWEEN;
192}
193
194/*
b8ae1f0f 195 * A static line, followed by a full-width combo box.
875b193f 196 */
b8ae1f0f 197void combobox(struct ctlpos *cp, char *text, int staticid, int listid)
875b193f 198{
199 RECT r;
875b193f 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 |
b8ae1f0f 211 CBS_DROPDOWN | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", listid);
875b193f 212
213 cp->ypos += STATICHEIGHT + GAPWITHIN + COMBOHEIGHT + GAPBETWEEN;
8c3cd914 214}
215
fe8abbf4 216struct radio { char *text; int id; };
217
218static void radioline_common(struct ctlpos *cp, char *text, int id,
219 int nacross, struct radio *buttons, int nbuttons)
32874aea 220{
8c3cd914 221 RECT r;
8c3cd914 222 int group;
223 int i;
fe8abbf4 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 }
8c3cd914 234
8c3cd914 235 group = WS_GROUP;
236 i = 0;
fe8abbf4 237 for (j = 0; j < nbuttons; j++) {
238 char *btext = buttons[j].text;
239 int bid = buttons[j].id;
240
32874aea 241 if (i == nacross) {
fe8abbf4 242 cp->ypos += r.bottom + (nacross > 1 ? GAPBETWEEN : GAPWITHIN);
32874aea 243 i = 0;
f37caa11 244 }
32874aea 245 r.left = GAPBETWEEN + i * (cp->width + GAPBETWEEN) / nacross;
fe8abbf4 246 if (j < nbuttons-1)
32874aea 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",
fe8abbf4 254 BS_NOTIFY | BS_AUTORADIOBUTTON | WS_CHILD |
255 WS_VISIBLE | WS_TABSTOP | group, 0, btext, bid);
32874aea 256 group = 0;
257 i++;
8c3cd914 258 }
8c3cd914 259 cp->ypos += r.bottom + GAPBETWEEN;
260}
261
262/*
d74d141c 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 */
276void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...)
277{
d74d141c 278 va_list ap;
fe8abbf4 279 struct radio *buttons;
280 int i, nbuttons;
d74d141c 281
d74d141c 282 va_start(ap, nacross);
fe8abbf4 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);
7bd02947 290 nbuttons++;
fe8abbf4 291 }
292 va_end(ap);
3d88e64d 293 buttons = snewn(nbuttons, struct radio);
fe8abbf4 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 }
d74d141c 299 va_end(ap);
fe8abbf4 300 radioline_common(cp, text, id, nacross, buttons, nbuttons);
301 sfree(buttons);
d74d141c 302}
303
304/*
305 * A set of radio buttons on the same line, without a static above
306 * them. Otherwise just like radioline.
307 */
308void bareradioline(struct ctlpos *cp, int nacross, ...)
309{
310 va_list ap;
fe8abbf4 311 struct radio *buttons;
312 int i, nbuttons;
d74d141c 313
314 va_start(ap, nacross);
fe8abbf4 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);
3d88e64d 324 buttons = snewn(nbuttons, struct radio);
fe8abbf4 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 }
d74d141c 330 va_end(ap);
fe8abbf4 331 radioline_common(cp, NULL, 0, nacross, buttons, nbuttons);
332 sfree(buttons);
d74d141c 333}
334
335/*
8c3cd914 336 * A set of radio buttons on multiple lines, with a static above
337 * them.
338 */
32874aea 339void radiobig(struct ctlpos *cp, char *text, int id, ...)
340{
8c3cd914 341 va_list ap;
fe8abbf4 342 struct radio *buttons;
343 int i, nbuttons;
8c3cd914 344
8c3cd914 345 va_start(ap, id);
fe8abbf4 346 nbuttons = 0;
8c3cd914 347 while (1) {
fe8abbf4 348 char *btext = va_arg(ap, char *);
32874aea 349 int bid;
32874aea 350 if (!btext)
351 break;
352 bid = va_arg(ap, int);
8c3cd914 353 }
354 va_end(ap);
3d88e64d 355 buttons = snewn(nbuttons, struct radio);
fe8abbf4 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);
8c3cd914 364}
365
366/*
367 * A single standalone checkbox.
368 */
32874aea 369void checkbox(struct ctlpos *cp, char *text, int id)
370{
8c3cd914 371 RECT r;
372
32874aea 373 r.left = GAPBETWEEN;
374 r.top = cp->ypos;
375 r.right = cp->width;
376 r.bottom = CHECKBOXHEIGHT;
8c3cd914 377 cp->ypos += r.bottom + GAPBETWEEN;
378 doctl(cp, r, "BUTTON",
fe8abbf4 379 BS_NOTIFY | BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0,
32874aea 380 text, id);
8c3cd914 381}
382
383/*
fe8abbf4 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 */
388char *staticwrap(struct ctlpos *cp, HWND hwnd, char *text, int *lines)
389{
fe8abbf4 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;
97332719 397 HFONT oldfont, newfont;
fe8abbf4 398
3d88e64d 399 ret = snewn(1+strlen(text), char);
fe8abbf4 400 p = text;
401 q = ret;
3d88e64d 402 pwidths = snewn(1+strlen(text), INT);
fe8abbf4 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.
fe8abbf4 408 */
97332719 409 SetMapMode(hdc, MM_TEXT); /* ensure logical units == pixels */
fe8abbf4 410 r.left = r.top = r.bottom = 0;
411 r.right = cp->width;
412 MapDialogRect(hwnd, &r);
97332719 413 width = r.right;
fe8abbf4 414
415 nlines = 1;
416
97332719 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
fe8abbf4 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
97332719 462 SelectObject(hdc, oldfont);
fe8abbf4 463 ReleaseDC(cp->hwnd, hdc);
464
465 if (lines) *lines = nlines;
466
467 return ret;
468}
469
470/*
6e522441 471 * A single standalone static text control.
472 */
66ee282a 473void statictext(struct ctlpos *cp, char *text, int lines, int id)
32874aea 474{
6e522441 475 RECT r;
476
32874aea 477 r.left = GAPBETWEEN;
478 r.top = cp->ypos;
479 r.right = cp->width;
66ee282a 480 r.bottom = STATICHEIGHT * lines;
6e522441 481 cp->ypos += r.bottom + GAPBETWEEN;
fe8abbf4 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 */
490void 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);
6e522441 501}
502
503/*
8c3cd914 504 * A button on the right hand side, with a static to its left.
505 */
506void staticbtn(struct ctlpos *cp, char *stext, int sid,
32874aea 507 char *btext, int bid)
508{
8c3cd914 509 const int height = (PUSHBTNHEIGHT > STATICHEIGHT ?
32874aea 510 PUSHBTNHEIGHT : STATICHEIGHT);
8c3cd914 511 RECT r;
512 int lwid, rwid, rpos;
513
514 rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
32874aea 515 lwid = rpos - 2 * GAPBETWEEN;
8c3cd914 516 rwid = cp->width + GAPBETWEEN - rpos;
517
32874aea 518 r.left = GAPBETWEEN;
519 r.top = cp->ypos + (height - STATICHEIGHT) / 2;
520 r.right = lwid;
521 r.bottom = STATICHEIGHT;
8c3cd914 522 doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
523
32874aea 524 r.left = rpos;
525 r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
526 r.right = rwid;
527 r.bottom = PUSHBTNHEIGHT;
8c3cd914 528 doctl(cp, r, "BUTTON",
fe8abbf4 529 BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
32874aea 530 0, btext, bid);
8c3cd914 531
532 cp->ypos += height + GAPBETWEEN;
533}
534
535/*
fe8abbf4 536 * A simple push button.
537 */
538void 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/*
af282e3b 561 * Like staticbtn, but two buttons.
562 */
563void 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",
fe8abbf4 588 BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
af282e3b 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",
fe8abbf4 596 BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
af282e3b 597 0, btext2, bid2);
598
599 cp->ypos += height + GAPBETWEEN;
600}
601
602/*
8c3cd914 603 * An edit control on the right hand side, with a static to its left.
604 */
6e522441 605static void staticedit_internal(struct ctlpos *cp, char *stext,
32874aea 606 int sid, int eid, int percentedit,
607 int style)
608{
8c3cd914 609 const int height = (EDITHEIGHT > STATICHEIGHT ?
32874aea 610 EDITHEIGHT : STATICHEIGHT);
8c3cd914 611 RECT r;
612 int lwid, rwid, rpos;
613
32874aea 614 rpos =
615 GAPBETWEEN + (100 - percentedit) * (cp->width + GAPBETWEEN) / 100;
616 lwid = rpos - 2 * GAPBETWEEN;
8c3cd914 617 rwid = cp->width + GAPBETWEEN - rpos;
618
32874aea 619 r.left = GAPBETWEEN;
620 r.top = cp->ypos + (height - STATICHEIGHT) / 2;
621 r.right = lwid;
622 r.bottom = STATICHEIGHT;
8c3cd914 623 doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
624
32874aea 625 r.left = rpos;
626 r.top = cp->ypos + (height - EDITHEIGHT) / 2;
627 r.right = rwid;
628 r.bottom = EDITHEIGHT;
8c3cd914 629 doctl(cp, r, "EDIT",
32874aea 630 WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | style,
631 WS_EX_CLIENTEDGE, "", eid);
8c3cd914 632
633 cp->ypos += height + GAPBETWEEN;
634}
635
6e522441 636void staticedit(struct ctlpos *cp, char *stext,
32874aea 637 int sid, int eid, int percentedit)
638{
6e522441 639 staticedit_internal(cp, stext, sid, eid, percentedit, 0);
640}
641
642void staticpassedit(struct ctlpos *cp, char *stext,
32874aea 643 int sid, int eid, int percentedit)
644{
6e522441 645 staticedit_internal(cp, stext, sid, eid, percentedit, ES_PASSWORD);
646}
647
648/*
2c9c6388 649 * A drop-down list box on the right hand side, with a static to
650 * its left.
651 */
652void 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/*
fe8abbf4 683 * A combo box on the right hand side, with a static to its left.
684 */
685void 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 */
718void 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/*
6e522441 741 * A big multiline edit control with a static labelling it.
742 */
743void bigeditctrl(struct ctlpos *cp, char *stext,
32874aea 744 int sid, int eid, int lines)
745{
6e522441 746 RECT r;
747
32874aea 748 r.left = GAPBETWEEN;
749 r.top = cp->ypos;
750 r.right = cp->width;
751 r.bottom = STATICHEIGHT;
6e522441 752 cp->ypos += r.bottom + GAPWITHIN;
753 doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
754
32874aea 755 r.left = GAPBETWEEN;
756 r.top = cp->ypos;
757 r.right = cp->width;
758 r.bottom = EDITHEIGHT + (lines - 1) * STATICHEIGHT;
6e522441 759 cp->ypos += r.bottom + GAPBETWEEN;
760 doctl(cp, r, "EDIT",
32874aea 761 WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | ES_MULTILINE,
762 WS_EX_CLIENTEDGE, "", eid);
6e522441 763}
764
8c3cd914 765/*
fe8abbf4 766 * A list box with a static labelling it.
767 */
768void 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/*
8c3cd914 795 * A tab-control substitute when a real tab control is unavailable.
796 */
32874aea 797void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id)
798{
8c3cd914 799 const int height = (COMBOHEIGHT > STATICHEIGHT ?
32874aea 800 COMBOHEIGHT : STATICHEIGHT);
8c3cd914 801 RECT r;
802 int bigwid, lwid, rwid, rpos;
803 static const int BIGGAP = 15;
804 static const int MEDGAP = 3;
805
32874aea 806 bigwid = cp->width + 2 * GAPBETWEEN - 2 * BIGGAP;
8c3cd914 807 cp->ypos += MEDGAP;
808 rpos = BIGGAP + (bigwid + BIGGAP) / 2;
32874aea 809 lwid = rpos - 2 * BIGGAP;
8c3cd914 810 rwid = bigwid + BIGGAP - rpos;
811
32874aea 812 r.left = BIGGAP;
813 r.top = cp->ypos + (height - STATICHEIGHT) / 2;
814 r.right = lwid;
815 r.bottom = STATICHEIGHT;
8c3cd914 816 doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
817
32874aea 818 r.left = rpos;
819 r.top = cp->ypos + (height - COMBOHEIGHT) / 2;
820 r.right = rwid;
821 r.bottom = COMBOHEIGHT * 10;
8c3cd914 822 doctl(cp, r, "COMBOBOX",
32874aea 823 WS_CHILD | WS_VISIBLE | WS_TABSTOP |
824 CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
8c3cd914 825
826 cp->ypos += height + MEDGAP + GAPBETWEEN;
827
32874aea 828 r.left = GAPBETWEEN;
829 r.top = cp->ypos;
830 r.right = cp->width;
831 r.bottom = 2;
8c3cd914 832 doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
32874aea 833 0, "", s2id);
8c3cd914 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 */
840void editbutton(struct ctlpos *cp, char *stext, int sid,
32874aea 841 int eid, char *btext, int bid)
842{
8c3cd914 843 const int height = (EDITHEIGHT > PUSHBTNHEIGHT ?
32874aea 844 EDITHEIGHT : PUSHBTNHEIGHT);
8c3cd914 845 RECT r;
846 int lwid, rwid, rpos;
847
32874aea 848 r.left = GAPBETWEEN;
849 r.top = cp->ypos;
850 r.right = cp->width;
851 r.bottom = STATICHEIGHT;
8c3cd914 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;
32874aea 856 lwid = rpos - 2 * GAPBETWEEN;
8c3cd914 857 rwid = cp->width + GAPBETWEEN - rpos;
858
32874aea 859 r.left = GAPBETWEEN;
860 r.top = cp->ypos + (height - EDITHEIGHT) / 2;
861 r.right = lwid;
862 r.bottom = EDITHEIGHT;
8c3cd914 863 doctl(cp, r, "EDIT",
32874aea 864 WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
865 WS_EX_CLIENTEDGE, "", eid);
8c3cd914 866
32874aea 867 r.left = rpos;
868 r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
869 r.right = rwid;
870 r.bottom = PUSHBTNHEIGHT;
8c3cd914 871 doctl(cp, r, "BUTTON",
fe8abbf4 872 BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
32874aea 873 0, btext, bid);
8c3cd914 874
875 cp->ypos += height + GAPBETWEEN;
876}
877
878/*
fe8abbf4 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.
8c3cd914 882 */
fe8abbf4 883void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines,
884 char *stext, int sid, int listid, int upbid, int dnbid)
32874aea 885{
fe8abbf4 886 const static int percents[] = { 5, 75, 20 };
8c3cd914 887 RECT r;
fe8abbf4 888 int xpos, percent = 0, i;
889 int listheight = LISTHEIGHT + (lines - 1) * LISTINCREMENT;
890 const int BTNSHEIGHT = 2*PUSHBTNHEIGHT + GAPBETWEEN;
891 int totalheight, buttonpos;
8c3cd914 892
fe8abbf4 893 /* Squirrel away IDs. */
894 hdl->listid = listid;
895 hdl->upbid = upbid;
896 hdl->dnbid = dnbid;
8c3cd914 897
fe8abbf4 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);
8c3cd914 906 }
907
fe8abbf4 908 if (listheight > BTNSHEIGHT) {
909 totalheight = listheight;
910 buttonpos = (listheight - BTNSHEIGHT) / 2;
911 } else {
912 totalheight = BTNSHEIGHT;
913 buttonpos = 0;
ca20bfcf 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;
fe8abbf4 928 r.top = cp->ypos; r.bottom = listheight;
ca20bfcf 929 {
930 HWND ctl;
931 ctl = doctl(cp, r, "LISTBOX",
932 WS_CHILD | WS_VISIBLE | WS_TABSTOP |
fe8abbf4 933 WS_VSCROLL | LBS_HASSTRINGS | LBS_USETABSTOPS,
ca20bfcf 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;
fe8abbf4 945 r.top = cp->ypos + buttonpos; r.bottom = PUSHBTNHEIGHT;
ca20bfcf 946 doctl(cp, r, "BUTTON",
fe8abbf4 947 BS_NOTIFY | WS_CHILD | WS_VISIBLE |
948 WS_TABSTOP | BS_PUSHBUTTON,
ca20bfcf 949 0, "&Up", upbid);
950
951 r.left = left; r.right = wid;
fe8abbf4 952 r.top = cp->ypos + buttonpos + PUSHBTNHEIGHT + GAPBETWEEN;
ca20bfcf 953 r.bottom = PUSHBTNHEIGHT;
954 doctl(cp, r, "BUTTON",
fe8abbf4 955 BS_NOTIFY | WS_CHILD | WS_VISIBLE |
956 WS_TABSTOP | BS_PUSHBUTTON,
ca20bfcf 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 */
971static 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);
3d88e64d 977 txt = snewn(tlen+1, char);
ca20bfcf 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
994int 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);
ca20bfcf 1012 if (ret == -1)
1013 return ret;
1014 ret = LBItemFromPt(hwnd, cursor, FALSE);
ca20bfcf 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.
fe8abbf4 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.
ca20bfcf 1038 */
1039int 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;
fe8abbf4 1045 int ret = 0;
ca20bfcf 1046
1047 if (is_dlmsg) {
1048
cdcbdf3b 1049 if ((int)wParam == hdl->listid) {
ca20bfcf 1050 DRAGLISTINFO *dlm = (DRAGLISTINFO *)lParam;
d1582b2e 1051 int dest = 0; /* initialise to placate gcc */
ca20bfcf 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);
fe8abbf4 1062 ret |= 1; break;
ca20bfcf 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;
fe8abbf4 1068 ret |= 1; break;
ca20bfcf 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);
fe8abbf4 1078 ret |= 1; break;
ca20bfcf 1079 case DL_DROPPED:
2b241edd 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 }
ca20bfcf 1085 SendDlgItemMessage(hwnd, hdl->listid,
1086 LB_DELETESTRING, hdl->dummyitem, 0);
2b241edd 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 }
fe8abbf4 1094 ret |= 2;
ca20bfcf 1095 }
fe8abbf4 1096 ret |= 1; break;
ca20bfcf 1097 }
1098 }
1099
1100 } else {
1101
ca20bfcf 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);
fe8abbf4 1120 ret |= 2;
ca20bfcf 1121 }
1122
1123 }
1124
1125 }
1126
fe8abbf4 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 }
ca20bfcf 1133
1134 return ret;
ca20bfcf 1135}
1136
1137/*
6e522441 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 */
32874aea 1142void progressbar(struct ctlpos *cp, int id)
1143{
6e522441 1144 RECT r;
1145
32874aea 1146 r.left = GAPBETWEEN;
1147 r.top = cp->ypos;
1148 r.right = cp->width;
1149 r.bottom = PROGBARHEIGHT;
6e522441 1150 cp->ypos += r.bottom + GAPBETWEEN;
1151
32874aea 1152 doctl(cp, r, PROGRESS_CLASS, WS_CHILD | WS_VISIBLE
6e522441 1153#ifdef PBS_SMOOTH
32874aea 1154 | PBS_SMOOTH
6e522441 1155#endif
32874aea 1156 , WS_EX_CLIENTEDGE, "", id);
6e522441 1157}
d74d141c 1158
fe8abbf4 1159/* ----------------------------------------------------------------------
1160 * Platform-specific side of portable dialog-box mechanism.
1161 */
1162
d74d141c 1163/*
fe8abbf4 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.
d74d141c 1171 */
fe8abbf4 1172static 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
3d88e64d 1180 ret = snewn(2*strlen(text)+1, char); /* size potentially doubles! */
fe8abbf4 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}
d74d141c 1198
fe8abbf4 1199void 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
1210void 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
1221static 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}
1232static 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}
1243static 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}
1254static 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
1266void winctrl_init(struct winctrls *wc)
1267{
1268 wc->byctrl = newtree234(winctrl_cmp_byctrl);
1269 wc->byid = newtree234(winctrl_cmp_byid);
1270}
1271void 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 }
d74d141c 1280
fe8abbf4 1281 freetree234(wc->byctrl);
1282 freetree234(wc->byid);
1283 wc->byctrl = wc->byid = NULL;
1284}
1285
1286void 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
1297void 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
1305struct winctrl *winctrl_findbyctrl(struct winctrls *wc, union control *ctrl)
1306{
1307 return find234(wc->byctrl, ctrl, winctrl_cmp_byctrl_find);
1308}
1309
1310struct winctrl *winctrl_findbyid(struct winctrls *wc, int id)
1311{
1312 return find234(wc->byid, &id, winctrl_cmp_byid_find);
1313}
1314
1315struct winctrl *winctrl_findbyindex(struct winctrls *wc, int index)
1316{
1317 return index234(wc->byid, index);
1318}
1319
1320void 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
d1582b2e 1332 char shortcuts[MAX_SHORTCUTS_PER_CTRL];
1333 int nshortcuts;
fe8abbf4 1334 char *escaped;
0bd8d76d 1335 int i, actual_base_id, base_id, num_ids;
fe8abbf4 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) {
3d88e64d 1342 struct winctrl *c = snew(struct winctrl);
fe8abbf4 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) {
3d88e64d 1355 struct winctrl *c = snew(struct winctrl);
fe8abbf4 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
fe8abbf4 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 {
a4b92c62 1414 /*
fe8abbf4 1415 * We're recombining the various columns into one.
a4b92c62 1416 */
fe8abbf4 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;
fe8abbf4 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
d1582b2e 1441 colstart = colspan = -1; /* indicate this was tab-delayed */
1442
fe8abbf4 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;
d74d141c 1473 }
1474 }
fe8abbf4 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
0bd8d76d 1483 /* Almost all controls start at base_id. */
1484 actual_base_id = base_id;
1485
fe8abbf4 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
3d88e64d 1538 buttons = snewn(ctrl->radio.nbuttons, struct radio);
fe8abbf4 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;
0bd8d76d 1576 if (ctrl->button.iscancel)
1577 actual_base_id = IDCANCEL;
fe8abbf4 1578 num_ids = 1;
0bd8d76d 1579 button(&pos, escaped, actual_base_id, ctrl->button.isdefault);
fe8abbf4 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) {
3d88e64d 1588 data = snew(struct prefslist);
fe8abbf4 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
3d88e64d 1620 tabarray = snewn(ctrl->listbox.ncols-1, int);
fe8abbf4 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);
3d88e64d 1650 data = snew(FontSpec);
fe8abbf4 1651 break;
1652 default:
1653 assert(!"Can't happen");
d1582b2e 1654 num_ids = 0; /* placate gcc */
fe8abbf4 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) {
3d88e64d 1664 struct winctrl *c = snew(struct winctrl);
fe8abbf4 1665
1666 c->ctrl = ctrl;
0bd8d76d 1667 c->base_id = actual_base_id;
fe8abbf4 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);
0bd8d76d 1673 if (actual_base_id == base_id)
1674 base_id += num_ids;
fe8abbf4 1675 }
1676
d1582b2e 1677 if (colstart >= 0) {
fe8abbf4 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 }
d74d141c 1686 }
fe8abbf4 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
1702static 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
0bd8d76d 1715union control *dlg_last_focused(union control *ctrl, void *dlg)
fe8abbf4 1716{
1717 struct dlgparam *dp = (struct dlgparam *)dlg;
0bd8d76d 1718 return dp->focused == ctrl ? dp->lastfocused : dp->focused;
fe8abbf4 1719}
1720
1721/*
1722 * The dialog-box procedure calls this function to handle Windows
1723 * messages on a control we manage.
1724 */
1725int 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 */
d1582b2e 1746 c = NULL;
fe8abbf4 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
97332719 1764 SetMapMode(hdc, MM_TEXT); /* ensure logical units == pixels */
1765
fe8abbf4 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);
3d88e64d 1818 text = snewn(len+1, char);
fe8abbf4 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 &&
d1582b2e 1844 (HIWORD(wParam) == BN_CLICKED ||
1845 HIWORD(wParam) == BN_DOUBLECLICKED) &&
fe8abbf4 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);
875e0b16 1903 if (msg == WM_COMMAND && id == 1 && HIWORD(wParam) == EN_CHANGE)
1904 ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
fe8abbf4 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 */
2028int 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 */
d1582b2e 2037 c = NULL;
fe8abbf4 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
2064static 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
2076void 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
2087int 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
2100void 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
2108int 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
2116void 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
2124void 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. */
2134void 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 ||
d1582b2e 2141 (c->ctrl->generic.type == CTRL_EDITBOX &&
2142 c->ctrl->editbox.has_list)));
fe8abbf4 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
2148void 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 ||
d1582b2e 2155 (c->ctrl->generic.type == CTRL_EDITBOX &&
2156 c->ctrl->editbox.has_list)));
fe8abbf4 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
2162void 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 ||
d1582b2e 2169 (c->ctrl->generic.type == CTRL_EDITBOX &&
2170 c->ctrl->editbox.has_list)));
fe8abbf4 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 */
4eaff8d4 2183void dlg_listbox_addwithid(union control *ctrl, void *dlg,
2184 char const *text, int id)
fe8abbf4 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 ||
d1582b2e 2191 (c->ctrl->generic.type == CTRL_EDITBOX &&
2192 c->ctrl->editbox.has_list)));
fe8abbf4 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
2201int 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. */
2213int 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
2228int 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
2239void 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
2250void 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
2258void 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
2266void 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
2275void 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
2294void 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 */
2307void 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
2316void 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
2327void 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 */
2361void dlg_beep(void *dlg)
2362{
2363 /* struct dlgparam *dp = (struct dlgparam *)dlg; */
2364 MessageBeep(0);
2365}
2366
2367void 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 */
2380void dlg_end(void *dlg, int value)
2381{
2382 struct dlgparam *dp = (struct dlgparam *)dlg;
2383 dp->ended = TRUE;
2384 dp->endresult = value;
2385}
2386
2387void 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
2415void 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
2424int 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;
d74d141c 2435}
4e6d4091 2436
2437struct perctrl_privdata {
2438 union control *ctrl;
2439 void *data;
2440 int needs_free;
2441};
2442
2443static 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
2454void 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;
f6f450e2 2462 dp->wintitle = dp->errtitle = NULL;
4e6d4091 2463 dp->privdata = newtree234(perctrl_privdata_cmp);
2464}
2465
2466void 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
2472void 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 }
f6f450e2 2486 sfree(dp->wintitle);
2487 sfree(dp->errtitle);
4e6d4091 2488}
2489
2490void *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
2502void 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) {
3d88e64d 2509 p = snew(struct perctrl_privdata);
4e6d4091 2510 p->ctrl = ctrl;
2511 p->needs_free = FALSE;
2512 add234(dp->privdata, p);
2513 }
2514 p->data = ptr;
2515}
2516
2517void *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) {
3d88e64d 2524 p = snew(struct perctrl_privdata);
4e6d4091 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;
3d88e64d 2531 /*
2532 * This is an internal allocation routine, so it's allowed to
2533 * use smalloc directly.
2534 */
4e6d4091 2535 p->data = smalloc(size);
2536 return p->data;
2537}