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