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