Game configuration box for Windows, by constructing the dialog box
[sgt/puzzles] / windows.c
1 /*
2 * windows.c: Windows front end for my puzzle collection.
3 */
4
5 #include <windows.h>
6 #include <commctrl.h>
7
8 #include <stdio.h>
9 #include <assert.h>
10 #include <stdarg.h>
11 #include <stdlib.h>
12 #include <time.h>
13
14 #include "puzzles.h"
15
16 #define IDM_NEW 0x0010
17 #define IDM_RESTART 0x0020
18 #define IDM_UNDO 0x0030
19 #define IDM_REDO 0x0040
20 #define IDM_QUIT 0x0050
21 #define IDM_CONFIG 0x0060
22 #define IDM_PRESETS 0x0100
23
24 #ifdef DEBUG
25 static FILE *debug_fp = NULL;
26 static HANDLE debug_hdl = INVALID_HANDLE_VALUE;
27 static int debug_got_console = 0;
28
29 void dputs(char *buf)
30 {
31 DWORD dw;
32
33 if (!debug_got_console) {
34 if (AllocConsole()) {
35 debug_got_console = 1;
36 debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE);
37 }
38 }
39 if (!debug_fp) {
40 debug_fp = fopen("debug.log", "w");
41 }
42
43 if (debug_hdl != INVALID_HANDLE_VALUE) {
44 WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL);
45 }
46 fputs(buf, debug_fp);
47 fflush(debug_fp);
48 }
49
50 void debug_printf(char *fmt, ...)
51 {
52 char buf[4096];
53 va_list ap;
54
55 va_start(ap, fmt);
56 vsprintf(buf, fmt, ap);
57 dputs(buf);
58 va_end(ap);
59 }
60
61 #define debug(x) (debug_printf x)
62
63 #else
64
65 #define debug(x)
66
67 #endif
68
69 struct font {
70 HFONT font;
71 int type;
72 int size;
73 };
74
75 struct cfg_aux {
76 int ctlid;
77 };
78
79 struct frontend {
80 midend_data *me;
81 HWND hwnd, statusbar, cfgbox;
82 HINSTANCE inst;
83 HBITMAP bitmap, prevbm;
84 HDC hdc_bm;
85 COLORREF *colours;
86 HBRUSH *brushes;
87 HPEN *pens;
88 HRGN clip;
89 UINT timer;
90 int npresets;
91 game_params **presets;
92 struct font *fonts;
93 int nfonts, fontsize;
94 config_item *cfg;
95 struct cfg_aux *cfgaux;
96 int cfg_done;
97 HFONT cfgfont;
98 };
99
100 void fatal(char *fmt, ...)
101 {
102 char buf[2048];
103 va_list ap;
104
105 va_start(ap, fmt);
106 vsprintf(buf, fmt, ap);
107 va_end(ap);
108
109 MessageBox(NULL, buf, "Fatal error", MB_ICONEXCLAMATION | MB_OK);
110
111 exit(1);
112 }
113
114 void status_bar(frontend *fe, char *text)
115 {
116 SetWindowText(fe->statusbar, text);
117 }
118
119 void frontend_default_colour(frontend *fe, float *output)
120 {
121 DWORD c = GetSysColor(COLOR_MENU); /* ick */
122
123 output[0] = (float)(GetRValue(c) / 255.0);
124 output[1] = (float)(GetGValue(c) / 255.0);
125 output[2] = (float)(GetBValue(c) / 255.0);
126 }
127
128 void clip(frontend *fe, int x, int y, int w, int h)
129 {
130 if (!fe->clip) {
131 fe->clip = CreateRectRgn(0, 0, 1, 1);
132 GetClipRgn(fe->hdc_bm, fe->clip);
133 }
134
135 IntersectClipRect(fe->hdc_bm, x, y, x+w, y+h);
136 }
137
138 void unclip(frontend *fe)
139 {
140 assert(fe->clip);
141 SelectClipRgn(fe->hdc_bm, fe->clip);
142 }
143
144 void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
145 int align, int colour, char *text)
146 {
147 int i;
148
149 /*
150 * Find or create the font.
151 */
152 for (i = 0; i < fe->nfonts; i++)
153 if (fe->fonts[i].type == fonttype && fe->fonts[i].size == fontsize)
154 break;
155
156 if (i == fe->nfonts) {
157 if (fe->fontsize <= fe->nfonts) {
158 fe->fontsize = fe->nfonts + 10;
159 fe->fonts = sresize(fe->fonts, fe->fontsize, struct font);
160 }
161
162 fe->nfonts++;
163
164 fe->fonts[i].type = fonttype;
165 fe->fonts[i].size = fontsize;
166
167 /*
168 * FIXME: Really I should make at least _some_ effort to
169 * pick the correct font.
170 */
171 fe->fonts[i].font = CreateFont(-fontsize, 0, 0, 0, 0,
172 FALSE, FALSE, FALSE, DEFAULT_CHARSET,
173 OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
174 DEFAULT_QUALITY,
175 (fonttype == FONT_FIXED ?
176 FIXED_PITCH | FF_DONTCARE :
177 VARIABLE_PITCH | FF_SWISS),
178 NULL);
179 }
180
181 /*
182 * Position and draw the text.
183 */
184 {
185 HFONT oldfont;
186 TEXTMETRIC tm;
187 SIZE size;
188
189 oldfont = SelectObject(fe->hdc_bm, fe->fonts[i].font);
190 if (GetTextMetrics(fe->hdc_bm, &tm)) {
191 if (align & ALIGN_VCENTRE)
192 y -= (tm.tmAscent+tm.tmDescent)/2;
193 else
194 y -= tm.tmAscent;
195 }
196 if (GetTextExtentPoint32(fe->hdc_bm, text, strlen(text), &size)) {
197 if (align & ALIGN_HCENTRE)
198 x -= size.cx / 2;
199 else if (align & ALIGN_HRIGHT)
200 x -= size.cx;
201 }
202 SetBkMode(fe->hdc_bm, TRANSPARENT);
203 TextOut(fe->hdc_bm, x, y, text, strlen(text));
204 SelectObject(fe->hdc_bm, oldfont);
205 }
206 }
207
208 void draw_rect(frontend *fe, int x, int y, int w, int h, int colour)
209 {
210 if (w == 1 && h == 1) {
211 /*
212 * Rectangle() appears to get uppity if asked to draw a 1x1
213 * rectangle, presumably on the grounds that that's beneath
214 * its dignity and you ought to be using SetPixel instead.
215 * So I will.
216 */
217 SetPixel(fe->hdc_bm, x, y, fe->colours[colour]);
218 } else {
219 HBRUSH oldbrush = SelectObject(fe->hdc_bm, fe->brushes[colour]);
220 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
221 Rectangle(fe->hdc_bm, x, y, x+w, y+h);
222 SelectObject(fe->hdc_bm, oldbrush);
223 SelectObject(fe->hdc_bm, oldpen);
224 }
225 }
226
227 void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour)
228 {
229 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
230 MoveToEx(fe->hdc_bm, x1, y1, NULL);
231 LineTo(fe->hdc_bm, x2, y2);
232 SetPixel(fe->hdc_bm, x2, y2, fe->colours[colour]);
233 SelectObject(fe->hdc_bm, oldpen);
234 }
235
236 void draw_polygon(frontend *fe, int *coords, int npoints,
237 int fill, int colour)
238 {
239 POINT *pts = snewn(npoints+1, POINT);
240 int i;
241
242 for (i = 0; i <= npoints; i++) {
243 int j = (i < npoints ? i : 0);
244 pts[i].x = coords[j*2];
245 pts[i].y = coords[j*2+1];
246 }
247
248 if (fill) {
249 HBRUSH oldbrush = SelectObject(fe->hdc_bm, fe->brushes[colour]);
250 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
251 Polygon(fe->hdc_bm, pts, npoints);
252 SelectObject(fe->hdc_bm, oldbrush);
253 SelectObject(fe->hdc_bm, oldpen);
254 } else {
255 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
256 Polyline(fe->hdc_bm, pts, npoints+1);
257 SelectObject(fe->hdc_bm, oldpen);
258 }
259
260 sfree(pts);
261 }
262
263 void start_draw(frontend *fe)
264 {
265 HDC hdc_win;
266 hdc_win = GetDC(fe->hwnd);
267 fe->hdc_bm = CreateCompatibleDC(hdc_win);
268 fe->prevbm = SelectObject(fe->hdc_bm, fe->bitmap);
269 ReleaseDC(fe->hwnd, hdc_win);
270 fe->clip = NULL;
271 SetMapMode(fe->hdc_bm, MM_TEXT);
272 }
273
274 void draw_update(frontend *fe, int x, int y, int w, int h)
275 {
276 RECT r;
277
278 r.left = x;
279 r.top = y;
280 r.right = x + w;
281 r.bottom = y + h;
282
283 InvalidateRect(fe->hwnd, &r, FALSE);
284 }
285
286 void end_draw(frontend *fe)
287 {
288 SelectObject(fe->hdc_bm, fe->prevbm);
289 DeleteDC(fe->hdc_bm);
290 if (fe->clip) {
291 DeleteObject(fe->clip);
292 fe->clip = NULL;
293 }
294 }
295
296 void deactivate_timer(frontend *fe)
297 {
298 KillTimer(fe->hwnd, fe->timer);
299 fe->timer = 0;
300 }
301
302 void activate_timer(frontend *fe)
303 {
304 fe->timer = SetTimer(fe->hwnd, fe->timer, 20, NULL);
305 }
306
307 static frontend *new_window(HINSTANCE inst)
308 {
309 frontend *fe;
310 int x, y;
311 RECT r, sr;
312 HDC hdc;
313
314 fe = snew(frontend);
315 fe->me = midend_new(fe);
316 fe->inst = inst;
317 midend_new_game(fe->me, NULL);
318 midend_size(fe->me, &x, &y);
319
320 fe->timer = 0;
321
322 {
323 int i, ncolours;
324 float *colours;
325
326 colours = midend_colours(fe->me, &ncolours);
327
328 fe->colours = snewn(ncolours, COLORREF);
329 fe->brushes = snewn(ncolours, HBRUSH);
330 fe->pens = snewn(ncolours, HPEN);
331
332 for (i = 0; i < ncolours; i++) {
333 fe->colours[i] = RGB(255 * colours[i*3+0],
334 255 * colours[i*3+1],
335 255 * colours[i*3+2]);
336 fe->brushes[i] = CreateSolidBrush(fe->colours[i]);
337 fe->pens[i] = CreatePen(PS_SOLID, 1, fe->colours[i]);
338 }
339 }
340
341 r.left = r.top = 0;
342 r.right = x;
343 r.bottom = y;
344 AdjustWindowRectEx(&r, WS_OVERLAPPEDWINDOW &~
345 (WS_THICKFRAME | WS_MAXIMIZEBOX | WS_OVERLAPPED),
346 TRUE, 0);
347
348 fe->hwnd = CreateWindowEx(0, game_name, game_name,
349 WS_OVERLAPPEDWINDOW &~
350 (WS_THICKFRAME | WS_MAXIMIZEBOX),
351 CW_USEDEFAULT, CW_USEDEFAULT,
352 r.right - r.left, r.bottom - r.top,
353 NULL, NULL, inst, NULL);
354
355 {
356 HMENU bar = CreateMenu();
357 HMENU menu = CreateMenu();
358
359 AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)menu, "Game");
360 AppendMenu(menu, MF_ENABLED, IDM_NEW, "New");
361 AppendMenu(menu, MF_ENABLED, IDM_RESTART, "Restart");
362
363 if ((fe->npresets = midend_num_presets(fe->me)) > 0 ||
364 game_can_configure) {
365 HMENU sub = CreateMenu();
366 int i;
367
368 AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)sub, "Type");
369
370 fe->presets = snewn(fe->npresets, game_params *);
371
372 for (i = 0; i < fe->npresets; i++) {
373 char *name;
374
375 midend_fetch_preset(fe->me, i, &name, &fe->presets[i]);
376
377 /*
378 * FIXME: we ought to go through and do something
379 * with ampersands here.
380 */
381
382 AppendMenu(sub, MF_ENABLED, IDM_PRESETS + 0x10 * i, name);
383 }
384
385 if (game_can_configure) {
386 AppendMenu(sub, MF_ENABLED, IDM_CONFIG, "Custom...");
387 }
388 }
389
390 AppendMenu(menu, MF_SEPARATOR, 0, 0);
391 AppendMenu(menu, MF_ENABLED, IDM_UNDO, "Undo");
392 AppendMenu(menu, MF_ENABLED, IDM_REDO, "Redo");
393 AppendMenu(menu, MF_SEPARATOR, 0, 0);
394 AppendMenu(menu, MF_ENABLED, IDM_QUIT, "Exit");
395 SetMenu(fe->hwnd, bar);
396 }
397
398 if (midend_wants_statusbar(fe->me)) {
399 fe->statusbar = CreateWindowEx(0, STATUSCLASSNAME, "ooh",
400 WS_CHILD | WS_VISIBLE,
401 0, 0, 0, 0, /* status bar does these */
402 fe->hwnd, NULL, inst, NULL);
403 GetWindowRect(fe->statusbar, &sr);
404 SetWindowPos(fe->hwnd, NULL, 0, 0,
405 r.right - r.left, r.bottom - r.top + sr.bottom - sr.top,
406 SWP_NOMOVE | SWP_NOZORDER);
407 SetWindowPos(fe->statusbar, NULL, 0, y, x, sr.bottom - sr.top,
408 SWP_NOZORDER);
409 } else {
410 fe->statusbar = NULL;
411 }
412
413 hdc = GetDC(fe->hwnd);
414 fe->bitmap = CreateCompatibleBitmap(hdc, x, y);
415 ReleaseDC(fe->hwnd, hdc);
416
417 SetWindowLong(fe->hwnd, GWL_USERDATA, (LONG)fe);
418
419 ShowWindow(fe->hwnd, SW_NORMAL);
420 SetForegroundWindow(fe->hwnd);
421
422 midend_redraw(fe->me);
423
424 return fe;
425 }
426
427 static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg,
428 WPARAM wParam, LPARAM lParam)
429 {
430 frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
431 config_item *i;
432 struct cfg_aux *j;
433
434 switch (msg) {
435 case WM_INITDIALOG:
436 return 0;
437
438 case WM_COMMAND:
439 /*
440 * OK and Cancel are special cases.
441 */
442 if ((HIWORD(wParam) == BN_CLICKED ||
443 HIWORD(wParam) == BN_DOUBLECLICKED) &&
444 (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)) {
445 if (LOWORD(wParam) == IDOK) {
446 char *err = midend_set_config(fe->me, fe->cfg);
447
448 if (err) {
449 MessageBox(hwnd, err, "Validation error",
450 MB_ICONERROR | MB_OK);
451 } else {
452 fe->cfg_done = 2;
453 }
454 } else {
455 fe->cfg_done = 1;
456 }
457 return 0;
458 }
459
460 /*
461 * First find the control whose id this is.
462 */
463 for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++) {
464 if (j->ctlid == LOWORD(wParam))
465 break;
466 }
467 if (i->type == C_END)
468 return 0; /* not our problem */
469
470 if (i->type == C_STRING && HIWORD(wParam) == EN_CHANGE) {
471 char buffer[4096];
472 GetDlgItemText(fe->cfgbox, j->ctlid, buffer, lenof(buffer));
473 buffer[lenof(buffer)-1] = '\0';
474 sfree(i->sval);
475 i->sval = dupstr(buffer);
476 } else if (i->type == C_BOOLEAN &&
477 (HIWORD(wParam) == BN_CLICKED ||
478 HIWORD(wParam) == BN_DOUBLECLICKED)) {
479 i->ival = IsDlgButtonChecked(fe->cfgbox, j->ctlid);
480 } else if (i->type == C_CHOICES &&
481 HIWORD(wParam) == CBN_SELCHANGE) {
482 i->ival = SendDlgItemMessage(fe->cfgbox, j->ctlid,
483 CB_GETCURSEL, 0, 0);
484 }
485
486 return 0;
487
488 case WM_CLOSE:
489 fe->cfg_done = 1;
490 return 0;
491 }
492
493 return 0;
494 }
495
496 HWND mkctrl(frontend *fe, int x1, int x2, int y1, int y2,
497 char *wclass, int wstyle,
498 int exstyle, char *wtext, int wid)
499 {
500 HWND ret;
501 ret = CreateWindowEx(exstyle, wclass, wtext,
502 wstyle | WS_CHILD | WS_VISIBLE, x1, y1, x2-x1, y2-y1,
503 fe->cfgbox, (HMENU) wid, fe->inst, NULL);
504 SendMessage(ret, WM_SETFONT, (WPARAM)fe->cfgfont, MAKELPARAM(TRUE, 0));
505 return ret;
506 }
507
508 static int get_config(frontend *fe)
509 {
510 config_item *i;
511 struct cfg_aux *j;
512 WNDCLASS wc;
513 MSG msg;
514 TEXTMETRIC tm;
515 HDC hdc;
516 HFONT oldfont;
517 SIZE size;
518 HWND ctl;
519 int gm, id, nctrls;
520 int winwidth, winheight, col1l, col1r, col2l, col2r, y;
521 int height, width, maxlabel, maxcheckbox;
522
523 wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW;
524 wc.lpfnWndProc = DefDlgProc;
525 wc.cbClsExtra = 0;
526 wc.cbWndExtra = DLGWINDOWEXTRA + 8;
527 wc.hInstance = fe->inst;
528 wc.hIcon = NULL;
529 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
530 wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
531 wc.lpszMenuName = NULL;
532 wc.lpszClassName = "GameConfigBox";
533 RegisterClass(&wc);
534
535 hdc = GetDC(fe->hwnd);
536 SetMapMode(hdc, MM_TEXT);
537
538 fe->cfg_done = FALSE;
539
540 fe->cfgfont = CreateFont(-MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72),
541 0, 0, 0, 0,
542 FALSE, FALSE, FALSE, DEFAULT_CHARSET,
543 OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
544 DEFAULT_QUALITY,
545 FF_SWISS,
546 "MS Shell Dlg");
547
548 oldfont = SelectObject(hdc, fe->cfgfont);
549 if (GetTextMetrics(hdc, &tm)) {
550 height = tm.tmAscent + tm.tmDescent;
551 width = tm.tmAveCharWidth;
552 } else {
553 height = width = 30;
554 }
555
556 fe->cfg = midend_get_config(fe->me);
557
558 /*
559 * Figure out the layout of the config box by measuring the
560 * length of each piece of text.
561 */
562 maxlabel = maxcheckbox = 0;
563 winheight = height/2;
564
565 for (i = fe->cfg; i->type != C_END; i++) {
566 switch (i->type) {
567 case C_STRING:
568 case C_CHOICES:
569 /*
570 * Both these control types have a label filling only
571 * the left-hand column of the box.
572 */
573 if (GetTextExtentPoint32(hdc, i->name, strlen(i->name), &size) &&
574 maxlabel < size.cx)
575 maxlabel = size.cx;
576 winheight += height * 3 / 2 + (height / 2);
577 break;
578
579 case C_BOOLEAN:
580 /*
581 * Checkboxes take up the whole of the box width.
582 */
583 if (GetTextExtentPoint32(hdc, i->name, strlen(i->name), &size) &&
584 maxcheckbox < size.cx)
585 maxcheckbox = size.cx;
586 winheight += height + (height / 2);
587 break;
588 }
589 }
590
591 winheight += height + height * 7 / 4; /* OK / Cancel buttons */
592
593 col1l = 2*width;
594 col1r = col1l + maxlabel;
595 col2l = col1r + 2*width;
596 col2r = col2l + 30*width;
597 if (col2r < col1l+2*height+maxcheckbox)
598 col2r = col1l+2*height+maxcheckbox;
599 winwidth = col2r + 2*width;
600
601 ReleaseDC(fe->hwnd, hdc);
602
603 /*
604 * Create the dialog, now that we know its size.
605 */
606 {
607 RECT r, r2;
608
609 r.left = r.top = 0;
610 r.right = winwidth;
611 r.bottom = winheight;
612
613 AdjustWindowRectEx(&r, (WS_OVERLAPPEDWINDOW /*|
614 DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
615 WS_CAPTION | WS_SYSMENU*/) &~
616 (WS_MAXIMIZEBOX | WS_OVERLAPPED),
617 FALSE, 0);
618
619 /*
620 * Centre the dialog on its parent window.
621 */
622 r.right -= r.left;
623 r.bottom -= r.top;
624 GetWindowRect(fe->hwnd, &r2);
625 r.left = (r2.left + r2.right - r.right) / 2;
626 r.top = (r2.top + r2.bottom - r.bottom) / 2;
627 r.right += r.left;
628 r.bottom += r.top;
629
630 fe->cfgbox = CreateWindowEx(0, wc.lpszClassName, "Configuration",
631 DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
632 WS_CAPTION | WS_SYSMENU,
633 r.left, r.top,
634 r.right-r.left, r.bottom-r.top,
635 fe->hwnd, NULL, fe->inst, NULL);
636 }
637
638 SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE);
639
640 SetWindowLong(fe->cfgbox, GWL_USERDATA, (LONG)fe);
641 SetWindowLong(fe->cfgbox, DWL_DLGPROC, (LONG)ConfigDlgProc);
642
643 /*
644 * Count the controls so we can allocate cfgaux.
645 */
646 for (nctrls = 0, i = fe->cfg; i->type != C_END; i++)
647 nctrls++;
648 fe->cfgaux = snewn(nctrls, struct cfg_aux);
649
650 id = 1000;
651 y = height/2;
652 for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++) {
653 switch (i->type) {
654 case C_STRING:
655 /*
656 * Edit box with a label beside it.
657 */
658 mkctrl(fe, col1l, col1r, y+height*1/8, y+height*9/8,
659 "Static", 0, 0, i->name, id++);
660 ctl = mkctrl(fe, col2l, col2r, y, y+height*3/2,
661 "EDIT", WS_TABSTOP | ES_AUTOHSCROLL,
662 WS_EX_CLIENTEDGE, "", (j->ctlid = id++));
663 SetWindowText(ctl, i->sval);
664 y += height*3/2;
665 break;
666
667 case C_BOOLEAN:
668 /*
669 * Simple checkbox.
670 */
671 mkctrl(fe, col1l, col2r, y, y+height, "BUTTON",
672 BS_NOTIFY | BS_AUTOCHECKBOX | WS_TABSTOP,
673 0, i->name, (j->ctlid = id++));
674 y += height;
675 break;
676
677 case C_CHOICES:
678 /*
679 * Drop-down list with a label beside it.
680 */
681 mkctrl(fe, col1l, col1r, y+height*1/8, y+height*9/8,
682 "STATIC", 0, 0, i->name, id++);
683 ctl = mkctrl(fe, col2l, col2r, y, y+height*41/2,
684 "COMBOBOX", WS_TABSTOP |
685 CBS_DROPDOWNLIST | CBS_HASSTRINGS,
686 WS_EX_CLIENTEDGE, "", (j->ctlid = id++));
687 {
688 char c, *p, *q, *str;
689
690 SendMessage(ctl, CB_RESETCONTENT, 0, 0);
691 p = i->sval;
692 c = *p++;
693 while (*p) {
694 q = p;
695 while (*q && *q != c) q++;
696 str = snewn(q-p+1, char);
697 strncpy(str, p, q-p);
698 str[q-p] = '\0';
699 SendMessage(ctl, CB_ADDSTRING, 0, (LPARAM)str);
700 sfree(str);
701 if (*q) q++;
702 p = q;
703 }
704 }
705
706 SendMessage(ctl, CB_SETCURSEL, i->ival, 0);
707
708 y += height*3/2;
709 break;
710 }
711
712 assert(y < winheight);
713 y += height/2;
714 }
715
716 y += height/2; /* extra space before OK and Cancel */
717 mkctrl(fe, col1l, (col1l+col2r)/2-width, y, y+height*7/4, "BUTTON",
718 BS_PUSHBUTTON | BS_NOTIFY | WS_TABSTOP | BS_DEFPUSHBUTTON, 0,
719 "OK", IDOK);
720 mkctrl(fe, (col1l+col2r)/2+width, col2r, y, y+height*7/4, "BUTTON",
721 BS_PUSHBUTTON | BS_NOTIFY | WS_TABSTOP, 0, "Cancel", IDCANCEL);
722
723 SendMessage(fe->cfgbox, WM_INITDIALOG, 0, 0);
724
725 EnableWindow(fe->hwnd, FALSE);
726 ShowWindow(fe->cfgbox, SW_NORMAL);
727 while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
728 if (!IsDialogMessage(fe->cfgbox, &msg))
729 DispatchMessage(&msg);
730 if (fe->cfg_done)
731 break;
732 }
733 EnableWindow(fe->hwnd, TRUE);
734 SetForegroundWindow(fe->hwnd);
735 DestroyWindow(fe->cfgbox);
736 DeleteObject(fe->cfgfont);
737
738 free_cfg(fe->cfg);
739 sfree(fe->cfgaux);
740
741 return (fe->cfg_done == 2);
742 }
743
744 static void new_game_type(frontend *fe)
745 {
746 RECT r, sr;
747 HDC hdc;
748 int x, y;
749
750 midend_new_game(fe->me, NULL);
751 midend_size(fe->me, &x, &y);
752
753 r.left = r.top = 0;
754 r.right = x;
755 r.bottom = y;
756 AdjustWindowRectEx(&r, WS_OVERLAPPEDWINDOW &~
757 (WS_THICKFRAME | WS_MAXIMIZEBOX |
758 WS_OVERLAPPED),
759 TRUE, 0);
760
761 if (fe->statusbar != NULL) {
762 GetWindowRect(fe->statusbar, &sr);
763 } else {
764 sr.left = sr.right = sr.top = sr.bottom = 0;
765 }
766 SetWindowPos(fe->hwnd, NULL, 0, 0,
767 r.right - r.left,
768 r.bottom - r.top + sr.bottom - sr.top,
769 SWP_NOMOVE | SWP_NOZORDER);
770 if (fe->statusbar != NULL)
771 SetWindowPos(fe->statusbar, NULL, 0, y, x,
772 sr.bottom - sr.top, SWP_NOZORDER);
773
774 DeleteObject(fe->bitmap);
775
776 hdc = GetDC(fe->hwnd);
777 fe->bitmap = CreateCompatibleBitmap(hdc, x, y);
778 ReleaseDC(fe->hwnd, hdc);
779
780 midend_redraw(fe->me);
781 }
782
783 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
784 WPARAM wParam, LPARAM lParam)
785 {
786 frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
787
788 switch (message) {
789 case WM_CLOSE:
790 DestroyWindow(hwnd);
791 return 0;
792 case WM_COMMAND:
793 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
794 case IDM_NEW:
795 if (!midend_process_key(fe->me, 0, 0, 'n'))
796 PostQuitMessage(0);
797 break;
798 case IDM_RESTART:
799 if (!midend_process_key(fe->me, 0, 0, 'r'))
800 PostQuitMessage(0);
801 break;
802 case IDM_UNDO:
803 if (!midend_process_key(fe->me, 0, 0, 'u'))
804 PostQuitMessage(0);
805 break;
806 case IDM_REDO:
807 if (!midend_process_key(fe->me, 0, 0, '\x12'))
808 PostQuitMessage(0);
809 break;
810 case IDM_QUIT:
811 if (!midend_process_key(fe->me, 0, 0, 'q'))
812 PostQuitMessage(0);
813 break;
814 case IDM_CONFIG:
815 if (get_config(fe))
816 new_game_type(fe);
817 break;
818 default:
819 {
820 int p = ((wParam &~ 0xF) - IDM_PRESETS) / 0x10;
821
822 if (p >= 0 && p < fe->npresets) {
823 midend_set_params(fe->me, fe->presets[p]);
824 new_game_type(fe);
825 }
826 }
827 break;
828 }
829 break;
830 case WM_DESTROY:
831 PostQuitMessage(0);
832 return 0;
833 case WM_PAINT:
834 {
835 PAINTSTRUCT p;
836 HDC hdc, hdc2;
837 HBITMAP prevbm;
838
839 hdc = BeginPaint(hwnd, &p);
840 hdc2 = CreateCompatibleDC(hdc);
841 prevbm = SelectObject(hdc2, fe->bitmap);
842 BitBlt(hdc,
843 p.rcPaint.left, p.rcPaint.top,
844 p.rcPaint.right - p.rcPaint.left,
845 p.rcPaint.bottom - p.rcPaint.top,
846 hdc2,
847 p.rcPaint.left, p.rcPaint.top,
848 SRCCOPY);
849 SelectObject(hdc2, prevbm);
850 DeleteDC(hdc2);
851 EndPaint(hwnd, &p);
852 }
853 return 0;
854 case WM_KEYDOWN:
855 {
856 int key = -1;
857
858 switch (wParam) {
859 case VK_LEFT: key = CURSOR_LEFT; break;
860 case VK_RIGHT: key = CURSOR_RIGHT; break;
861 case VK_UP: key = CURSOR_UP; break;
862 case VK_DOWN: key = CURSOR_DOWN; break;
863 /*
864 * Diagonal keys on the numeric keypad.
865 */
866 case VK_PRIOR:
867 if (!(lParam & 0x01000000)) key = CURSOR_UP_RIGHT;
868 break;
869 case VK_NEXT:
870 if (!(lParam & 0x01000000)) key = CURSOR_DOWN_RIGHT;
871 break;
872 case VK_HOME:
873 if (!(lParam & 0x01000000)) key = CURSOR_UP_LEFT;
874 break;
875 case VK_END:
876 if (!(lParam & 0x01000000)) key = CURSOR_DOWN_LEFT;
877 break;
878 /*
879 * Numeric keypad keys with Num Lock on.
880 */
881 case VK_NUMPAD4: key = CURSOR_LEFT; break;
882 case VK_NUMPAD6: key = CURSOR_RIGHT; break;
883 case VK_NUMPAD8: key = CURSOR_UP; break;
884 case VK_NUMPAD2: key = CURSOR_DOWN; break;
885 case VK_NUMPAD9: key = CURSOR_UP_RIGHT; break;
886 case VK_NUMPAD3: key = CURSOR_DOWN_RIGHT; break;
887 case VK_NUMPAD7: key = CURSOR_UP_LEFT; break;
888 case VK_NUMPAD1: key = CURSOR_DOWN_LEFT; break;
889 }
890
891 if (key != -1) {
892 if (!midend_process_key(fe->me, 0, 0, key))
893 PostQuitMessage(0);
894 } else {
895 MSG m;
896 m.hwnd = hwnd;
897 m.message = WM_KEYDOWN;
898 m.wParam = wParam;
899 m.lParam = lParam & 0xdfff;
900 TranslateMessage(&m);
901 }
902 }
903 break;
904 case WM_LBUTTONDOWN:
905 case WM_RBUTTONDOWN:
906 case WM_MBUTTONDOWN:
907 {
908 int button;
909
910 /*
911 * Shift-clicks count as middle-clicks, since otherwise
912 * two-button Windows users won't have any kind of
913 * middle click to use.
914 */
915 if (message == WM_MBUTTONDOWN || (wParam & MK_SHIFT))
916 button = MIDDLE_BUTTON;
917 else if (message == WM_LBUTTONDOWN)
918 button = LEFT_BUTTON;
919 else
920 button = RIGHT_BUTTON;
921
922 if (!midend_process_key(fe->me, LOWORD(lParam),
923 HIWORD(lParam), button))
924 PostQuitMessage(0);
925 }
926 break;
927 case WM_CHAR:
928 if (!midend_process_key(fe->me, 0, 0, (unsigned char)wParam))
929 PostQuitMessage(0);
930 return 0;
931 case WM_TIMER:
932 if (fe->timer)
933 midend_timer(fe->me, (float)0.02);
934 return 0;
935 }
936
937 return DefWindowProc(hwnd, message, wParam, lParam);
938 }
939
940 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
941 {
942 MSG msg;
943
944 srand(time(NULL));
945
946 InitCommonControls();
947
948 if (!prev) {
949 WNDCLASS wndclass;
950
951 wndclass.style = 0;
952 wndclass.lpfnWndProc = WndProc;
953 wndclass.cbClsExtra = 0;
954 wndclass.cbWndExtra = 0;
955 wndclass.hInstance = inst;
956 wndclass.hIcon = LoadIcon(inst, IDI_APPLICATION);
957 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
958 wndclass.hbrBackground = NULL;
959 wndclass.lpszMenuName = NULL;
960 wndclass.lpszClassName = game_name;
961
962 RegisterClass(&wndclass);
963 }
964
965 new_window(inst);
966
967 while (GetMessage(&msg, NULL, 0, 0)) {
968 DispatchMessage(&msg);
969 }
970
971 return msg.wParam;
972 }