chiark / gitweb /
Improved the limited shuffle mechanism in Sixteen and Twiddle. They
[sgt-puzzles.git] / windows.c
1 /*
2  * windows.c: Windows front end for my puzzle collection.
3  * 
4  * TODO:
5  * 
6  *  - Figure out what to do if a puzzle requests a size bigger than
7  *    the screen will take. In principle we could put scrollbars in
8  *    the window, although that would be pretty horrid. Another
9  *    option is to detect in advance that this will be a problem -
10  *    we can probably tell this using midend_size() before actually
11  *    generating the puzzle - and simply refuse to do it.
12  */
13
14 #include <windows.h>
15 #include <commctrl.h>
16
17 #include <stdio.h>
18 #include <assert.h>
19 #include <ctype.h>
20 #include <stdarg.h>
21 #include <stdlib.h>
22 #include <time.h>
23
24 #include "puzzles.h"
25
26 #define IDM_NEW       0x0010
27 #define IDM_RESTART   0x0020
28 #define IDM_UNDO      0x0030
29 #define IDM_REDO      0x0040
30 #define IDM_COPY      0x0050
31 #define IDM_SOLVE     0x0060
32 #define IDM_QUIT      0x0070
33 #define IDM_CONFIG    0x0080
34 #define IDM_DESC      0x0090
35 #define IDM_SEED      0x00A0
36 #define IDM_HELPC     0x00B0
37 #define IDM_GAMEHELP  0x00C0
38 #define IDM_ABOUT     0x00D0
39 #define IDM_PRESETS   0x0100
40
41 #define HELP_FILE_NAME  "puzzles.hlp"
42 #define HELP_CNT_NAME   "puzzles.cnt"
43
44 #ifdef DEBUG
45 static FILE *debug_fp = NULL;
46 static HANDLE debug_hdl = INVALID_HANDLE_VALUE;
47 static int debug_got_console = 0;
48
49 void dputs(char *buf)
50 {
51     DWORD dw;
52
53     if (!debug_got_console) {
54         if (AllocConsole()) {
55             debug_got_console = 1;
56             debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE);
57         }
58     }
59     if (!debug_fp) {
60         debug_fp = fopen("debug.log", "w");
61     }
62
63     if (debug_hdl != INVALID_HANDLE_VALUE) {
64         WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL);
65     }
66     fputs(buf, debug_fp);
67     fflush(debug_fp);
68 }
69
70 void debug_printf(char *fmt, ...)
71 {
72     char buf[4096];
73     va_list ap;
74
75     va_start(ap, fmt);
76     vsprintf(buf, fmt, ap);
77     dputs(buf);
78     va_end(ap);
79 }
80
81 #define debug(x) (debug_printf x)
82
83 #else
84
85 #define debug(x)
86
87 #endif
88
89 struct font {
90     HFONT font;
91     int type;
92     int size;
93 };
94
95 struct cfg_aux {
96     int ctlid;
97 };
98
99 struct frontend {
100     midend_data *me;
101     HWND hwnd, statusbar, cfgbox;
102     HINSTANCE inst;
103     HBITMAP bitmap, prevbm;
104     HDC hdc_bm;
105     COLORREF *colours;
106     HBRUSH *brushes;
107     HPEN *pens;
108     HRGN clip;
109     UINT timer;
110     DWORD timer_last_tickcount;
111     int npresets;
112     game_params **presets;
113     struct font *fonts;
114     int nfonts, fontsize;
115     config_item *cfg;
116     struct cfg_aux *cfgaux;
117     int cfg_which, dlg_done;
118     HFONT cfgfont;
119     char *help_path;
120     int help_has_contents;
121     char *laststatus;
122 };
123
124 void fatal(char *fmt, ...)
125 {
126     char buf[2048];
127     va_list ap;
128
129     va_start(ap, fmt);
130     vsprintf(buf, fmt, ap);
131     va_end(ap);
132
133     MessageBox(NULL, buf, "Fatal error", MB_ICONEXCLAMATION | MB_OK);
134
135     exit(1);
136 }
137
138 void get_random_seed(void **randseed, int *randseedsize)
139 {
140     time_t *tp = snew(time_t);
141     time(tp);
142     *randseed = (void *)tp;
143     *randseedsize = sizeof(time_t);
144 }
145
146 void status_bar(frontend *fe, char *text)
147 {
148     char *rewritten = midend_rewrite_statusbar(fe->me, text);
149     if (!fe->laststatus || strcmp(rewritten, fe->laststatus)) {
150         SetWindowText(fe->statusbar, rewritten);
151         sfree(fe->laststatus);
152         fe->laststatus = rewritten;
153     } else {
154         sfree(rewritten);
155     }
156 }
157
158 void frontend_default_colour(frontend *fe, float *output)
159 {
160     DWORD c = GetSysColor(COLOR_MENU); /* ick */
161
162     output[0] = (float)(GetRValue(c) / 255.0);
163     output[1] = (float)(GetGValue(c) / 255.0);
164     output[2] = (float)(GetBValue(c) / 255.0);
165 }
166
167 void clip(frontend *fe, int x, int y, int w, int h)
168 {
169     IntersectClipRect(fe->hdc_bm, x, y, x+w, y+h);
170 }
171
172 void unclip(frontend *fe)
173 {
174     SelectClipRgn(fe->hdc_bm, NULL);
175 }
176
177 void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
178                int align, int colour, char *text)
179 {
180     int i;
181
182     /*
183      * Find or create the font.
184      */
185     for (i = 0; i < fe->nfonts; i++)
186         if (fe->fonts[i].type == fonttype && fe->fonts[i].size == fontsize)
187             break;
188
189     if (i == fe->nfonts) {
190         if (fe->fontsize <= fe->nfonts) {
191             fe->fontsize = fe->nfonts + 10;
192             fe->fonts = sresize(fe->fonts, fe->fontsize, struct font);
193         }
194
195         fe->nfonts++;
196
197         fe->fonts[i].type = fonttype;
198         fe->fonts[i].size = fontsize;
199
200         fe->fonts[i].font = CreateFont(-fontsize, 0, 0, 0, FW_BOLD,
201                                        FALSE, FALSE, FALSE, DEFAULT_CHARSET,
202                                        OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
203                                        DEFAULT_QUALITY,
204                                        (fonttype == FONT_FIXED ?
205                                         FIXED_PITCH | FF_DONTCARE :
206                                         VARIABLE_PITCH | FF_SWISS),
207                                        NULL);
208     }
209
210     /*
211      * Position and draw the text.
212      */
213     {
214         HFONT oldfont;
215         TEXTMETRIC tm;
216         SIZE size;
217
218         oldfont = SelectObject(fe->hdc_bm, fe->fonts[i].font);
219         if (GetTextMetrics(fe->hdc_bm, &tm)) {
220             if (align & ALIGN_VCENTRE)
221                 y -= (tm.tmAscent+tm.tmDescent)/2;
222             else
223                 y -= tm.tmAscent;
224         }
225         if (GetTextExtentPoint32(fe->hdc_bm, text, strlen(text), &size)) {
226             if (align & ALIGN_HCENTRE)
227                 x -= size.cx / 2;
228             else if (align & ALIGN_HRIGHT)
229                 x -= size.cx;
230         }
231         SetBkMode(fe->hdc_bm, TRANSPARENT);
232         SetTextColor(fe->hdc_bm, fe->colours[colour]);
233         TextOut(fe->hdc_bm, x, y, text, strlen(text));
234         SelectObject(fe->hdc_bm, oldfont);
235     }
236 }
237
238 void draw_rect(frontend *fe, int x, int y, int w, int h, int colour)
239 {
240     if (w == 1 && h == 1) {
241         /*
242          * Rectangle() appears to get uppity if asked to draw a 1x1
243          * rectangle, presumably on the grounds that that's beneath
244          * its dignity and you ought to be using SetPixel instead.
245          * So I will.
246          */
247         SetPixel(fe->hdc_bm, x, y, fe->colours[colour]);
248     } else {
249         HBRUSH oldbrush = SelectObject(fe->hdc_bm, fe->brushes[colour]);
250         HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
251         Rectangle(fe->hdc_bm, x, y, x+w, y+h);
252         SelectObject(fe->hdc_bm, oldbrush);
253         SelectObject(fe->hdc_bm, oldpen);
254     }
255 }
256
257 void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour)
258 {
259     HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
260     MoveToEx(fe->hdc_bm, x1, y1, NULL);
261     LineTo(fe->hdc_bm, x2, y2);
262     SetPixel(fe->hdc_bm, x2, y2, fe->colours[colour]);
263     SelectObject(fe->hdc_bm, oldpen);
264 }
265
266 void draw_polygon(frontend *fe, int *coords, int npoints,
267                   int fill, int colour)
268 {
269     POINT *pts = snewn(npoints+1, POINT);
270     int i;
271
272     for (i = 0; i <= npoints; i++) {
273         int j = (i < npoints ? i : 0);
274         pts[i].x = coords[j*2];
275         pts[i].y = coords[j*2+1];
276     }
277
278     if (fill) {
279         HBRUSH oldbrush = SelectObject(fe->hdc_bm, fe->brushes[colour]);
280         HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
281         Polygon(fe->hdc_bm, pts, npoints);
282         SelectObject(fe->hdc_bm, oldbrush);
283         SelectObject(fe->hdc_bm, oldpen);
284     } else {
285         HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
286         Polyline(fe->hdc_bm, pts, npoints+1);
287         SelectObject(fe->hdc_bm, oldpen);
288     }
289
290     sfree(pts);
291 }
292
293 void start_draw(frontend *fe)
294 {
295     HDC hdc_win;
296     hdc_win = GetDC(fe->hwnd);
297     fe->hdc_bm = CreateCompatibleDC(hdc_win);
298     fe->prevbm = SelectObject(fe->hdc_bm, fe->bitmap);
299     ReleaseDC(fe->hwnd, hdc_win);
300     fe->clip = NULL;
301     SetMapMode(fe->hdc_bm, MM_TEXT);
302 }
303
304 void draw_update(frontend *fe, int x, int y, int w, int h)
305 {
306     RECT r;
307
308     r.left = x;
309     r.top = y;
310     r.right = x + w;
311     r.bottom = y + h;
312
313     InvalidateRect(fe->hwnd, &r, FALSE);
314 }
315
316 void end_draw(frontend *fe)
317 {
318     SelectObject(fe->hdc_bm, fe->prevbm);
319     DeleteDC(fe->hdc_bm);
320     if (fe->clip) {
321         DeleteObject(fe->clip);
322         fe->clip = NULL;
323     }
324 }
325
326 void deactivate_timer(frontend *fe)
327 {
328     KillTimer(fe->hwnd, fe->timer);
329     fe->timer = 0;
330 }
331
332 void activate_timer(frontend *fe)
333 {
334     if (!fe->timer) {
335         fe->timer = SetTimer(fe->hwnd, fe->timer, 20, NULL);
336         fe->timer_last_tickcount = GetTickCount();
337     }
338 }
339
340 void write_clip(HWND hwnd, char *data)
341 {
342     HGLOBAL clipdata;
343     int len, i, j;
344     char *data2;
345     void *lock;
346
347     /*
348      * Windows expects CRLF in the clipboard, so we must convert
349      * any \n that has come out of the puzzle backend.
350      */
351     len = 0;
352     for (i = 0; data[i]; i++) {
353         if (data[i] == '\n')
354             len++;
355         len++;
356     }
357     data2 = snewn(len+1, char);
358     j = 0;
359     for (i = 0; data[i]; i++) {
360         if (data[i] == '\n')
361             data2[j++] = '\r';
362         data2[j++] = data[i];
363     }
364     assert(j == len);
365     data2[j] = '\0';
366
367     clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
368     if (!clipdata)
369         return;
370     lock = GlobalLock(clipdata);
371     if (!lock)
372         return;
373     memcpy(lock, data2, len);
374     ((unsigned char *) lock)[len] = 0;
375     GlobalUnlock(clipdata);
376
377     if (OpenClipboard(hwnd)) {
378         EmptyClipboard();
379         SetClipboardData(CF_TEXT, clipdata);
380         CloseClipboard();
381     } else
382         GlobalFree(clipdata);
383
384     sfree(data2);
385 }
386
387 /*
388  * See if we can find a help file.
389  */
390 static void find_help_file(frontend *fe)
391 {
392     char b[2048], *p, *q, *r;
393     FILE *fp;
394     if (!fe->help_path) {
395         GetModuleFileName(NULL, b, sizeof(b) - 1);
396         r = b;
397         p = strrchr(b, '\\');
398         if (p && p >= r) r = p+1;
399         q = strrchr(b, ':');
400         if (q && q >= r) r = q+1;
401         strcpy(r, HELP_FILE_NAME);
402         if ( (fp = fopen(b, "r")) != NULL) {
403             fe->help_path = dupstr(b);
404             fclose(fp);
405         } else
406             fe->help_path = NULL;
407         strcpy(r, HELP_CNT_NAME);
408         if ( (fp = fopen(b, "r")) != NULL) {
409             fe->help_has_contents = TRUE;
410             fclose(fp);
411         } else
412             fe->help_has_contents = FALSE;
413     }
414 }
415
416 static frontend *new_window(HINSTANCE inst, char *game_id, char **error)
417 {
418     frontend *fe;
419     int x, y;
420     RECT r, sr;
421     HDC hdc;
422
423     fe = snew(frontend);
424
425     fe->me = midend_new(fe, &thegame);
426
427     if (game_id) {
428         *error = midend_game_id(fe->me, game_id);
429         if (*error) {
430             midend_free(fe->me);
431             sfree(fe);
432             return NULL;
433         }
434     }
435
436     fe->help_path = NULL;
437     find_help_file(fe);
438
439     fe->inst = inst;
440     midend_new_game(fe->me);
441     midend_size(fe->me, &x, &y);
442
443     fe->timer = 0;
444
445     fe->fonts = NULL;
446     fe->nfonts = fe->fontsize = 0;
447
448     fe->laststatus = NULL;
449
450     {
451         int i, ncolours;
452         float *colours;
453
454         colours = midend_colours(fe->me, &ncolours);
455
456         fe->colours = snewn(ncolours, COLORREF);
457         fe->brushes = snewn(ncolours, HBRUSH);
458         fe->pens = snewn(ncolours, HPEN);
459
460         for (i = 0; i < ncolours; i++) {
461             fe->colours[i] = RGB(255 * colours[i*3+0],
462                                  255 * colours[i*3+1],
463                                  255 * colours[i*3+2]);
464             fe->brushes[i] = CreateSolidBrush(fe->colours[i]);
465             fe->pens[i] = CreatePen(PS_SOLID, 1, fe->colours[i]);
466         }
467     }
468
469     r.left = r.top = 0;
470     r.right = x;
471     r.bottom = y;
472     AdjustWindowRectEx(&r, WS_OVERLAPPEDWINDOW &~
473                        (WS_THICKFRAME | WS_MAXIMIZEBOX | WS_OVERLAPPED),
474                        TRUE, 0);
475
476     fe->hwnd = CreateWindowEx(0, thegame.name, thegame.name,
477                               WS_OVERLAPPEDWINDOW &~
478                               (WS_THICKFRAME | WS_MAXIMIZEBOX),
479                               CW_USEDEFAULT, CW_USEDEFAULT,
480                               r.right - r.left, r.bottom - r.top,
481                               NULL, NULL, inst, NULL);
482
483     {
484         HMENU bar = CreateMenu();
485         HMENU menu = CreateMenu();
486
487         AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)menu, "Game");
488         AppendMenu(menu, MF_ENABLED, IDM_NEW, "New");
489         AppendMenu(menu, MF_ENABLED, IDM_RESTART, "Restart");
490         AppendMenu(menu, MF_ENABLED, IDM_DESC, "Specific...");
491         AppendMenu(menu, MF_ENABLED, IDM_SEED, "Random Seed...");
492
493         if ((fe->npresets = midend_num_presets(fe->me)) > 0 ||
494             thegame.can_configure) {
495             HMENU sub = CreateMenu();
496             int i;
497
498             AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)sub, "Type");
499
500             fe->presets = snewn(fe->npresets, game_params *);
501
502             for (i = 0; i < fe->npresets; i++) {
503                 char *name;
504
505                 midend_fetch_preset(fe->me, i, &name, &fe->presets[i]);
506
507                 /*
508                  * FIXME: we ought to go through and do something
509                  * with ampersands here.
510                  */
511
512                 AppendMenu(sub, MF_ENABLED, IDM_PRESETS + 0x10 * i, name);
513             }
514
515             if (thegame.can_configure) {
516                 AppendMenu(sub, MF_ENABLED, IDM_CONFIG, "Custom...");
517             }
518         }
519
520         AppendMenu(menu, MF_SEPARATOR, 0, 0);
521         AppendMenu(menu, MF_ENABLED, IDM_UNDO, "Undo");
522         AppendMenu(menu, MF_ENABLED, IDM_REDO, "Redo");
523         if (thegame.can_format_as_text) {
524             AppendMenu(menu, MF_SEPARATOR, 0, 0);
525             AppendMenu(menu, MF_ENABLED, IDM_COPY, "Copy");
526         }
527         if (thegame.can_solve) {
528             AppendMenu(menu, MF_SEPARATOR, 0, 0);
529             AppendMenu(menu, MF_ENABLED, IDM_SOLVE, "Solve");
530         }
531         AppendMenu(menu, MF_SEPARATOR, 0, 0);
532         AppendMenu(menu, MF_ENABLED, IDM_QUIT, "Exit");
533         menu = CreateMenu();
534         AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)menu, "Help");
535         AppendMenu(menu, MF_ENABLED, IDM_ABOUT, "About");
536         if (fe->help_path) {
537             AppendMenu(menu, MF_SEPARATOR, 0, 0);
538             AppendMenu(menu, MF_ENABLED, IDM_HELPC, "Contents");
539             if (thegame.winhelp_topic) {
540                 char *item;
541                 assert(thegame.name);
542                 item = snewn(9+strlen(thegame.name), char); /*ick*/
543                 sprintf(item, "Help on %s", thegame.name);
544                 AppendMenu(menu, MF_ENABLED, IDM_GAMEHELP, item);
545                 sfree(item);
546             }
547         }
548         SetMenu(fe->hwnd, bar);
549     }
550
551     if (midend_wants_statusbar(fe->me)) {
552         fe->statusbar = CreateWindowEx(0, STATUSCLASSNAME, "ooh",
553                                        WS_CHILD | WS_VISIBLE,
554                                        0, 0, 0, 0, /* status bar does these */
555                                        fe->hwnd, NULL, inst, NULL);
556         GetWindowRect(fe->statusbar, &sr);
557         SetWindowPos(fe->hwnd, NULL, 0, 0,
558                      r.right - r.left, r.bottom - r.top + sr.bottom - sr.top,
559                      SWP_NOMOVE | SWP_NOZORDER);
560         SetWindowPos(fe->statusbar, NULL, 0, y, x, sr.bottom - sr.top,
561                      SWP_NOZORDER);
562     } else {
563         fe->statusbar = NULL;
564     }
565
566     hdc = GetDC(fe->hwnd);
567     fe->bitmap = CreateCompatibleBitmap(hdc, x, y);
568     ReleaseDC(fe->hwnd, hdc);
569
570     SetWindowLong(fe->hwnd, GWL_USERDATA, (LONG)fe);
571
572     ShowWindow(fe->hwnd, SW_NORMAL);
573     SetForegroundWindow(fe->hwnd);
574
575     midend_redraw(fe->me);
576
577     return fe;
578 }
579
580 static int CALLBACK AboutDlgProc(HWND hwnd, UINT msg,
581                                  WPARAM wParam, LPARAM lParam)
582 {
583     frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
584
585     switch (msg) {
586       case WM_INITDIALOG:
587         return 0;
588
589       case WM_COMMAND:
590         if ((HIWORD(wParam) == BN_CLICKED ||
591              HIWORD(wParam) == BN_DOUBLECLICKED) &&
592             LOWORD(wParam) == IDOK)
593             fe->dlg_done = 1;
594         return 0;
595
596       case WM_CLOSE:
597         fe->dlg_done = 1;
598         return 0;
599     }
600
601     return 0;
602 }
603
604 static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg,
605                                   WPARAM wParam, LPARAM lParam)
606 {
607     frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
608     config_item *i;
609     struct cfg_aux *j;
610
611     switch (msg) {
612       case WM_INITDIALOG:
613         return 0;
614
615       case WM_COMMAND:
616         /*
617          * OK and Cancel are special cases.
618          */
619         if ((HIWORD(wParam) == BN_CLICKED ||
620              HIWORD(wParam) == BN_DOUBLECLICKED) &&
621             (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)) {
622             if (LOWORD(wParam) == IDOK) {
623                 char *err = midend_set_config(fe->me, fe->cfg_which, fe->cfg);
624
625                 if (err) {
626                     MessageBox(hwnd, err, "Validation error",
627                                MB_ICONERROR | MB_OK);
628                 } else {
629                     fe->dlg_done = 2;
630                 }
631             } else {
632                 fe->dlg_done = 1;
633             }
634             return 0;
635         }
636
637         /*
638          * First find the control whose id this is.
639          */
640         for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++) {
641             if (j->ctlid == LOWORD(wParam))
642                 break;
643         }
644         if (i->type == C_END)
645             return 0;                  /* not our problem */
646
647         if (i->type == C_STRING && HIWORD(wParam) == EN_CHANGE) {
648             char buffer[4096];
649             GetDlgItemText(fe->cfgbox, j->ctlid, buffer, lenof(buffer));
650             buffer[lenof(buffer)-1] = '\0';
651             sfree(i->sval);
652             i->sval = dupstr(buffer);
653         } else if (i->type == C_BOOLEAN && 
654                    (HIWORD(wParam) == BN_CLICKED ||
655                     HIWORD(wParam) == BN_DOUBLECLICKED)) {
656             i->ival = IsDlgButtonChecked(fe->cfgbox, j->ctlid);
657         } else if (i->type == C_CHOICES &&
658                    HIWORD(wParam) == CBN_SELCHANGE) {
659             i->ival = SendDlgItemMessage(fe->cfgbox, j->ctlid,
660                                          CB_GETCURSEL, 0, 0);
661         }
662
663         return 0;
664
665       case WM_CLOSE:
666         fe->dlg_done = 1;
667         return 0;
668     }
669
670     return 0;
671 }
672
673 HWND mkctrl(frontend *fe, int x1, int x2, int y1, int y2,
674             char *wclass, int wstyle,
675             int exstyle, const char *wtext, int wid)
676 {
677     HWND ret;
678     ret = CreateWindowEx(exstyle, wclass, wtext,
679                          wstyle | WS_CHILD | WS_VISIBLE, x1, y1, x2-x1, y2-y1,
680                          fe->cfgbox, (HMENU) wid, fe->inst, NULL);
681     SendMessage(ret, WM_SETFONT, (WPARAM)fe->cfgfont, MAKELPARAM(TRUE, 0));
682     return ret;
683 }
684
685 static void about(frontend *fe)
686 {
687     int i;
688     WNDCLASS wc;
689     MSG msg;
690     TEXTMETRIC tm;
691     HDC hdc;
692     HFONT oldfont;
693     SIZE size;
694     int gm, id;
695     int winwidth, winheight, y;
696     int height, width, maxwid;
697     const char *strings[16];
698     int lengths[16];
699     int nstrings = 0;
700     char titlebuf[512];
701
702     sprintf(titlebuf, "About %.250s", thegame.name);
703
704     strings[nstrings++] = thegame.name;
705     strings[nstrings++] = "from Simon Tatham's Portable Puzzle Collection";
706     strings[nstrings++] = ver;
707
708     wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW;
709     wc.lpfnWndProc = DefDlgProc;
710     wc.cbClsExtra = 0;
711     wc.cbWndExtra = DLGWINDOWEXTRA + 8;
712     wc.hInstance = fe->inst;
713     wc.hIcon = NULL;
714     wc.hCursor = LoadCursor(NULL, IDC_ARROW);
715     wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
716     wc.lpszMenuName = NULL;
717     wc.lpszClassName = "GameAboutBox";
718     RegisterClass(&wc);
719
720     hdc = GetDC(fe->hwnd);
721     SetMapMode(hdc, MM_TEXT);
722
723     fe->dlg_done = FALSE;
724
725     fe->cfgfont = CreateFont(-MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72),
726                              0, 0, 0, 0,
727                              FALSE, FALSE, FALSE, DEFAULT_CHARSET,
728                              OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
729                              DEFAULT_QUALITY,
730                              FF_SWISS,
731                              "MS Shell Dlg");
732
733     oldfont = SelectObject(hdc, fe->cfgfont);
734     if (GetTextMetrics(hdc, &tm)) {
735         height = tm.tmAscent + tm.tmDescent;
736         width = tm.tmAveCharWidth;
737     } else {
738         height = width = 30;
739     }
740
741     /*
742      * Figure out the layout of the About box by measuring the
743      * length of each piece of text.
744      */
745     maxwid = 0;
746     winheight = height/2;
747
748     for (i = 0; i < nstrings; i++) {
749         if (GetTextExtentPoint32(hdc, strings[i], strlen(strings[i]), &size))
750             lengths[i] = size.cx;
751         else
752             lengths[i] = 0;            /* *shrug* */
753         if (maxwid < lengths[i])
754             maxwid = lengths[i];
755         winheight += height * 3 / 2 + (height / 2);
756     }
757
758     winheight += height + height * 7 / 4;      /* OK button */
759     winwidth = maxwid + 4*width;
760
761     SelectObject(hdc, oldfont);
762     ReleaseDC(fe->hwnd, hdc);
763
764     /*
765      * Create the dialog, now that we know its size.
766      */
767     {
768         RECT r, r2;
769
770         r.left = r.top = 0;
771         r.right = winwidth;
772         r.bottom = winheight;
773
774         AdjustWindowRectEx(&r, (WS_OVERLAPPEDWINDOW /*|
775                                 DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
776                                 WS_CAPTION | WS_SYSMENU*/) &~
777                            (WS_MAXIMIZEBOX | WS_OVERLAPPED),
778                            FALSE, 0);
779
780         /*
781          * Centre the dialog on its parent window.
782          */
783         r.right -= r.left;
784         r.bottom -= r.top;
785         GetWindowRect(fe->hwnd, &r2);
786         r.left = (r2.left + r2.right - r.right) / 2;
787         r.top = (r2.top + r2.bottom - r.bottom) / 2;
788         r.right += r.left;
789         r.bottom += r.top;
790
791         fe->cfgbox = CreateWindowEx(0, wc.lpszClassName, titlebuf,
792                                     DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
793                                     WS_CAPTION | WS_SYSMENU,
794                                     r.left, r.top,
795                                     r.right-r.left, r.bottom-r.top,
796                                     fe->hwnd, NULL, fe->inst, NULL);
797     }
798
799     SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE);
800
801     SetWindowLong(fe->cfgbox, GWL_USERDATA, (LONG)fe);
802     SetWindowLong(fe->cfgbox, DWL_DLGPROC, (LONG)AboutDlgProc);
803
804     id = 1000;
805     y = height/2;
806     for (i = 0; i < nstrings; i++) {
807         int border = width*2 + (maxwid - lengths[i]) / 2;
808         mkctrl(fe, border, border+lengths[i], y+height*1/8, y+height*9/8,
809                "Static", 0, 0, strings[i], id++);
810         y += height*3/2;
811
812         assert(y < winheight);
813         y += height/2;
814     }
815
816     y += height/2;                     /* extra space before OK */
817     mkctrl(fe, width*2, maxwid+width*2, y, y+height*7/4, "BUTTON",
818            BS_PUSHBUTTON | BS_NOTIFY | WS_TABSTOP | BS_DEFPUSHBUTTON, 0,
819            "OK", IDOK);
820
821     SendMessage(fe->cfgbox, WM_INITDIALOG, 0, 0);
822
823     EnableWindow(fe->hwnd, FALSE);
824     ShowWindow(fe->cfgbox, SW_NORMAL);
825     while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
826         if (!IsDialogMessage(fe->cfgbox, &msg))
827             DispatchMessage(&msg);
828         if (fe->dlg_done)
829             break;
830     }
831     EnableWindow(fe->hwnd, TRUE);
832     SetForegroundWindow(fe->hwnd);
833     DestroyWindow(fe->cfgbox);
834     DeleteObject(fe->cfgfont);
835 }
836
837 static int get_config(frontend *fe, int which)
838 {
839     config_item *i;
840     struct cfg_aux *j;
841     char *title;
842     WNDCLASS wc;
843     MSG msg;
844     TEXTMETRIC tm;
845     HDC hdc;
846     HFONT oldfont;
847     SIZE size;
848     HWND ctl;
849     int gm, id, nctrls;
850     int winwidth, winheight, col1l, col1r, col2l, col2r, y;
851     int height, width, maxlabel, maxcheckbox;
852
853     wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW;
854     wc.lpfnWndProc = DefDlgProc;
855     wc.cbClsExtra = 0;
856     wc.cbWndExtra = DLGWINDOWEXTRA + 8;
857     wc.hInstance = fe->inst;
858     wc.hIcon = NULL;
859     wc.hCursor = LoadCursor(NULL, IDC_ARROW);
860     wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
861     wc.lpszMenuName = NULL;
862     wc.lpszClassName = "GameConfigBox";
863     RegisterClass(&wc);
864
865     hdc = GetDC(fe->hwnd);
866     SetMapMode(hdc, MM_TEXT);
867
868     fe->dlg_done = FALSE;
869
870     fe->cfgfont = CreateFont(-MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72),
871                              0, 0, 0, 0,
872                              FALSE, FALSE, FALSE, DEFAULT_CHARSET,
873                              OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
874                              DEFAULT_QUALITY,
875                              FF_SWISS,
876                              "MS Shell Dlg");
877
878     oldfont = SelectObject(hdc, fe->cfgfont);
879     if (GetTextMetrics(hdc, &tm)) {
880         height = tm.tmAscent + tm.tmDescent;
881         width = tm.tmAveCharWidth;
882     } else {
883         height = width = 30;
884     }
885
886     fe->cfg = midend_get_config(fe->me, which, &title);
887     fe->cfg_which = which;
888
889     /*
890      * Figure out the layout of the config box by measuring the
891      * length of each piece of text.
892      */
893     maxlabel = maxcheckbox = 0;
894     winheight = height/2;
895
896     for (i = fe->cfg; i->type != C_END; i++) {
897         switch (i->type) {
898           case C_STRING:
899           case C_CHOICES:
900             /*
901              * Both these control types have a label filling only
902              * the left-hand column of the box.
903              */
904             if (GetTextExtentPoint32(hdc, i->name, strlen(i->name), &size) &&
905                 maxlabel < size.cx)
906                 maxlabel = size.cx;
907             winheight += height * 3 / 2 + (height / 2);
908             break;
909
910           case C_BOOLEAN:
911             /*
912              * Checkboxes take up the whole of the box width.
913              */
914             if (GetTextExtentPoint32(hdc, i->name, strlen(i->name), &size) &&
915                 maxcheckbox < size.cx)
916                 maxcheckbox = size.cx;
917             winheight += height + (height / 2);
918             break;
919         }
920     }
921
922     winheight += height + height * 7 / 4;      /* OK / Cancel buttons */
923
924     col1l = 2*width;
925     col1r = col1l + maxlabel;
926     col2l = col1r + 2*width;
927     col2r = col2l + 30*width;
928     if (col2r < col1l+2*height+maxcheckbox)
929         col2r = col1l+2*height+maxcheckbox;
930     winwidth = col2r + 2*width;
931
932     SelectObject(hdc, oldfont);
933     ReleaseDC(fe->hwnd, hdc);
934
935     /*
936      * Create the dialog, now that we know its size.
937      */
938     {
939         RECT r, r2;
940
941         r.left = r.top = 0;
942         r.right = winwidth;
943         r.bottom = winheight;
944
945         AdjustWindowRectEx(&r, (WS_OVERLAPPEDWINDOW /*|
946                                 DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
947                                 WS_CAPTION | WS_SYSMENU*/) &~
948                            (WS_MAXIMIZEBOX | WS_OVERLAPPED),
949                            FALSE, 0);
950
951         /*
952          * Centre the dialog on its parent window.
953          */
954         r.right -= r.left;
955         r.bottom -= r.top;
956         GetWindowRect(fe->hwnd, &r2);
957         r.left = (r2.left + r2.right - r.right) / 2;
958         r.top = (r2.top + r2.bottom - r.bottom) / 2;
959         r.right += r.left;
960         r.bottom += r.top;
961
962         fe->cfgbox = CreateWindowEx(0, wc.lpszClassName, title,
963                                     DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
964                                     WS_CAPTION | WS_SYSMENU,
965                                     r.left, r.top,
966                                     r.right-r.left, r.bottom-r.top,
967                                     fe->hwnd, NULL, fe->inst, NULL);
968         sfree(title);
969     }
970
971     SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE);
972
973     SetWindowLong(fe->cfgbox, GWL_USERDATA, (LONG)fe);
974     SetWindowLong(fe->cfgbox, DWL_DLGPROC, (LONG)ConfigDlgProc);
975
976     /*
977      * Count the controls so we can allocate cfgaux.
978      */
979     for (nctrls = 0, i = fe->cfg; i->type != C_END; i++)
980         nctrls++;
981     fe->cfgaux = snewn(nctrls, struct cfg_aux);
982
983     id = 1000;
984     y = height/2;
985     for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++) {
986         switch (i->type) {
987           case C_STRING:
988             /*
989              * Edit box with a label beside it.
990              */
991             mkctrl(fe, col1l, col1r, y+height*1/8, y+height*9/8,
992                    "Static", 0, 0, i->name, id++);
993             ctl = mkctrl(fe, col2l, col2r, y, y+height*3/2,
994                          "EDIT", WS_TABSTOP | ES_AUTOHSCROLL,
995                          WS_EX_CLIENTEDGE, "", (j->ctlid = id++));
996             SetWindowText(ctl, i->sval);
997             y += height*3/2;
998             break;
999
1000           case C_BOOLEAN:
1001             /*
1002              * Simple checkbox.
1003              */
1004             mkctrl(fe, col1l, col2r, y, y+height, "BUTTON",
1005                    BS_NOTIFY | BS_AUTOCHECKBOX | WS_TABSTOP,
1006                    0, i->name, (j->ctlid = id++));
1007             CheckDlgButton(fe->cfgbox, j->ctlid, (i->ival != 0));
1008             y += height;
1009             break;
1010
1011           case C_CHOICES:
1012             /*
1013              * Drop-down list with a label beside it.
1014              */
1015             mkctrl(fe, col1l, col1r, y+height*1/8, y+height*9/8,
1016                    "STATIC", 0, 0, i->name, id++);
1017             ctl = mkctrl(fe, col2l, col2r, y, y+height*41/2,
1018                          "COMBOBOX", WS_TABSTOP |
1019                          CBS_DROPDOWNLIST | CBS_HASSTRINGS,
1020                          WS_EX_CLIENTEDGE, "", (j->ctlid = id++));
1021             {
1022                 char c, *p, *q, *str;
1023
1024                 SendMessage(ctl, CB_RESETCONTENT, 0, 0);
1025                 p = i->sval;
1026                 c = *p++;
1027                 while (*p) {
1028                     q = p;
1029                     while (*q && *q != c) q++;
1030                     str = snewn(q-p+1, char);
1031                     strncpy(str, p, q-p);
1032                     str[q-p] = '\0';
1033                     SendMessage(ctl, CB_ADDSTRING, 0, (LPARAM)str);
1034                     sfree(str);
1035                     if (*q) q++;
1036                     p = q;
1037                 }
1038             }
1039
1040             SendMessage(ctl, CB_SETCURSEL, i->ival, 0);
1041
1042             y += height*3/2;
1043             break;
1044         }
1045
1046         assert(y < winheight);
1047         y += height/2;
1048     }
1049
1050     y += height/2;                     /* extra space before OK and Cancel */
1051     mkctrl(fe, col1l, (col1l+col2r)/2-width, y, y+height*7/4, "BUTTON",
1052            BS_PUSHBUTTON | BS_NOTIFY | WS_TABSTOP | BS_DEFPUSHBUTTON, 0,
1053            "OK", IDOK);
1054     mkctrl(fe, (col1l+col2r)/2+width, col2r, y, y+height*7/4, "BUTTON",
1055            BS_PUSHBUTTON | BS_NOTIFY | WS_TABSTOP, 0, "Cancel", IDCANCEL);
1056
1057     SendMessage(fe->cfgbox, WM_INITDIALOG, 0, 0);
1058
1059     EnableWindow(fe->hwnd, FALSE);
1060     ShowWindow(fe->cfgbox, SW_NORMAL);
1061     while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
1062         if (!IsDialogMessage(fe->cfgbox, &msg))
1063             DispatchMessage(&msg);
1064         if (fe->dlg_done)
1065             break;
1066     }
1067     EnableWindow(fe->hwnd, TRUE);
1068     SetForegroundWindow(fe->hwnd);
1069     DestroyWindow(fe->cfgbox);
1070     DeleteObject(fe->cfgfont);
1071
1072     free_cfg(fe->cfg);
1073     sfree(fe->cfgaux);
1074
1075     return (fe->dlg_done == 2);
1076 }
1077
1078 static void new_game_type(frontend *fe)
1079 {
1080     RECT r, sr;
1081     HDC hdc;
1082     int x, y;
1083
1084     midend_new_game(fe->me);
1085     midend_size(fe->me, &x, &y);
1086
1087     r.left = r.top = 0;
1088     r.right = x;
1089     r.bottom = y;
1090     AdjustWindowRectEx(&r, WS_OVERLAPPEDWINDOW &~
1091                        (WS_THICKFRAME | WS_MAXIMIZEBOX |
1092                         WS_OVERLAPPED),
1093                        TRUE, 0);
1094
1095     if (fe->statusbar != NULL) {
1096         GetWindowRect(fe->statusbar, &sr);
1097     } else {
1098         sr.left = sr.right = sr.top = sr.bottom = 0;
1099     }
1100     SetWindowPos(fe->hwnd, NULL, 0, 0,
1101                  r.right - r.left,
1102                  r.bottom - r.top + sr.bottom - sr.top,
1103                  SWP_NOMOVE | SWP_NOZORDER);
1104     if (fe->statusbar != NULL)
1105         SetWindowPos(fe->statusbar, NULL, 0, y, x,
1106                      sr.bottom - sr.top, SWP_NOZORDER);
1107
1108     DeleteObject(fe->bitmap);
1109
1110     hdc = GetDC(fe->hwnd);
1111     fe->bitmap = CreateCompatibleBitmap(hdc, x, y);
1112     ReleaseDC(fe->hwnd, hdc);
1113
1114     midend_redraw(fe->me);
1115 }
1116
1117 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
1118                                 WPARAM wParam, LPARAM lParam)
1119 {
1120     frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
1121
1122     switch (message) {
1123       case WM_CLOSE:
1124         DestroyWindow(hwnd);
1125         return 0;
1126       case WM_COMMAND:
1127         switch (wParam & ~0xF) {       /* low 4 bits reserved to Windows */
1128           case IDM_NEW:
1129             if (!midend_process_key(fe->me, 0, 0, 'n'))
1130                 PostQuitMessage(0);
1131             break;
1132           case IDM_RESTART:
1133             midend_restart_game(fe->me);
1134             break;
1135           case IDM_UNDO:
1136             if (!midend_process_key(fe->me, 0, 0, 'u'))
1137                 PostQuitMessage(0);
1138             break;
1139           case IDM_REDO:
1140             if (!midend_process_key(fe->me, 0, 0, '\x12'))
1141                 PostQuitMessage(0);
1142             break;
1143           case IDM_COPY:
1144             {
1145                 char *text = midend_text_format(fe->me);
1146                 if (text)
1147                     write_clip(hwnd, text);
1148                 else
1149                     MessageBeep(MB_ICONWARNING);
1150                 sfree(text);
1151             }
1152             break;
1153           case IDM_SOLVE:
1154             {
1155                 char *msg = midend_solve(fe->me);
1156                 if (msg)
1157                     MessageBox(hwnd, msg, "Unable to solve",
1158                                MB_ICONERROR | MB_OK);
1159             }
1160             break;
1161           case IDM_QUIT:
1162             if (!midend_process_key(fe->me, 0, 0, 'q'))
1163                 PostQuitMessage(0);
1164             break;
1165           case IDM_CONFIG:
1166             if (get_config(fe, CFG_SETTINGS))
1167                 new_game_type(fe);
1168             break;
1169           case IDM_SEED:
1170             if (get_config(fe, CFG_SEED))
1171                 new_game_type(fe);
1172             break;
1173           case IDM_DESC:
1174             if (get_config(fe, CFG_DESC))
1175                 new_game_type(fe);
1176             break;
1177           case IDM_ABOUT:
1178             about(fe);
1179             break;
1180           case IDM_HELPC:
1181             assert(fe->help_path);
1182             WinHelp(hwnd, fe->help_path,
1183                     fe->help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0);
1184             break;
1185           case IDM_GAMEHELP:
1186             assert(fe->help_path);
1187             assert(thegame.winhelp_topic);
1188             {
1189                 char *cmd = snewn(10+strlen(thegame.winhelp_topic), char);
1190                 sprintf(cmd, "JI(`',`%s')", thegame.winhelp_topic);
1191                 WinHelp(hwnd, fe->help_path, HELP_COMMAND, (DWORD)cmd);
1192                 sfree(cmd);
1193             }
1194             break;
1195           default:
1196             {
1197                 int p = ((wParam &~ 0xF) - IDM_PRESETS) / 0x10;
1198
1199                 if (p >= 0 && p < fe->npresets) {
1200                     midend_set_params(fe->me, fe->presets[p]);
1201                     new_game_type(fe);
1202                 }
1203             }
1204             break;
1205         }
1206         break;
1207       case WM_DESTROY:
1208         PostQuitMessage(0);
1209         return 0;
1210       case WM_PAINT:
1211         {
1212             PAINTSTRUCT p;
1213             HDC hdc, hdc2;
1214             HBITMAP prevbm;
1215
1216             hdc = BeginPaint(hwnd, &p);
1217             hdc2 = CreateCompatibleDC(hdc);
1218             prevbm = SelectObject(hdc2, fe->bitmap);
1219             BitBlt(hdc,
1220                    p.rcPaint.left, p.rcPaint.top,
1221                    p.rcPaint.right - p.rcPaint.left,
1222                    p.rcPaint.bottom - p.rcPaint.top,
1223                    hdc2,
1224                    p.rcPaint.left, p.rcPaint.top,
1225                    SRCCOPY);
1226             SelectObject(hdc2, prevbm);
1227             DeleteDC(hdc2);
1228             EndPaint(hwnd, &p);
1229         }
1230         return 0;
1231       case WM_KEYDOWN:
1232         {
1233             int key = -1;
1234             BYTE keystate[256];
1235             int r = GetKeyboardState(keystate);
1236             int shift = (r && (keystate[VK_SHIFT] & 0x80)) ? MOD_SHFT : 0;
1237             int ctrl = (r && (keystate[VK_CONTROL] & 0x80)) ? MOD_CTRL : 0;
1238
1239             switch (wParam) {
1240               case VK_LEFT:
1241                 if (!(lParam & 0x01000000))
1242                     key = MOD_NUM_KEYPAD | '4';
1243                 else
1244                     key = shift | ctrl | CURSOR_LEFT;
1245                 break;
1246               case VK_RIGHT:
1247                 if (!(lParam & 0x01000000))
1248                     key = MOD_NUM_KEYPAD | '6';
1249                 else
1250                     key = shift | ctrl | CURSOR_RIGHT;
1251                 break;
1252               case VK_UP:
1253                 if (!(lParam & 0x01000000))
1254                     key = MOD_NUM_KEYPAD | '8';
1255                 else
1256                     key = shift | ctrl | CURSOR_UP;
1257                 break;
1258               case VK_DOWN:
1259                 if (!(lParam & 0x01000000))
1260                     key = MOD_NUM_KEYPAD | '2';
1261                 else
1262                     key = shift | ctrl | CURSOR_DOWN;
1263                 break;
1264                 /*
1265                  * Diagonal keys on the numeric keypad.
1266                  */
1267               case VK_PRIOR:
1268                 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '9';
1269                 break;
1270               case VK_NEXT:
1271                 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '3';
1272                 break;
1273               case VK_HOME:
1274                 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '7';
1275                 break;
1276               case VK_END:
1277                 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '1';
1278                 break;
1279               case VK_INSERT:
1280                 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '0';
1281                 break;
1282               case VK_CLEAR:
1283                 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '5';
1284                 break;
1285                 /*
1286                  * Numeric keypad keys with Num Lock on.
1287                  */
1288               case VK_NUMPAD4: key = MOD_NUM_KEYPAD | '4'; break;
1289               case VK_NUMPAD6: key = MOD_NUM_KEYPAD | '6'; break;
1290               case VK_NUMPAD8: key = MOD_NUM_KEYPAD | '8'; break;
1291               case VK_NUMPAD2: key = MOD_NUM_KEYPAD | '2'; break;
1292               case VK_NUMPAD5: key = MOD_NUM_KEYPAD | '5'; break;
1293               case VK_NUMPAD9: key = MOD_NUM_KEYPAD | '9'; break;
1294               case VK_NUMPAD3: key = MOD_NUM_KEYPAD | '3'; break;
1295               case VK_NUMPAD7: key = MOD_NUM_KEYPAD | '7'; break;
1296               case VK_NUMPAD1: key = MOD_NUM_KEYPAD | '1'; break;
1297               case VK_NUMPAD0: key = MOD_NUM_KEYPAD | '0'; break;
1298             }
1299
1300             if (key != -1) {
1301                 if (!midend_process_key(fe->me, 0, 0, key))
1302                     PostQuitMessage(0);
1303             } else {
1304                 MSG m;
1305                 m.hwnd = hwnd;
1306                 m.message = WM_KEYDOWN;
1307                 m.wParam = wParam;
1308                 m.lParam = lParam & 0xdfff;
1309                 TranslateMessage(&m);
1310             }
1311         }
1312         break;
1313       case WM_LBUTTONDOWN:
1314       case WM_RBUTTONDOWN:
1315       case WM_MBUTTONDOWN:
1316         {
1317             int button;
1318
1319             /*
1320              * Shift-clicks count as middle-clicks, since otherwise
1321              * two-button Windows users won't have any kind of
1322              * middle click to use.
1323              */
1324             if (message == WM_MBUTTONDOWN || (wParam & MK_SHIFT))
1325                 button = MIDDLE_BUTTON;
1326             else if (message == WM_LBUTTONDOWN)
1327                 button = LEFT_BUTTON;
1328             else
1329                 button = RIGHT_BUTTON;
1330
1331             if (!midend_process_key(fe->me, (signed short)LOWORD(lParam),
1332                                     (signed short)HIWORD(lParam), button))
1333                 PostQuitMessage(0);
1334
1335             SetCapture(hwnd);
1336         }
1337         break;
1338       case WM_LBUTTONUP:
1339       case WM_RBUTTONUP:
1340       case WM_MBUTTONUP:
1341         {
1342             int button;
1343
1344             /*
1345              * Shift-clicks count as middle-clicks, since otherwise
1346              * two-button Windows users won't have any kind of
1347              * middle click to use.
1348              */
1349             if (message == WM_MBUTTONUP || (wParam & MK_SHIFT))
1350                 button = MIDDLE_RELEASE;
1351             else if (message == WM_LBUTTONUP)
1352                 button = LEFT_RELEASE;
1353             else
1354                 button = RIGHT_RELEASE;
1355
1356             if (!midend_process_key(fe->me, (signed short)LOWORD(lParam),
1357                                     (signed short)HIWORD(lParam), button))
1358                 PostQuitMessage(0);
1359
1360             ReleaseCapture();
1361         }
1362         break;
1363       case WM_MOUSEMOVE:
1364         {
1365             int button;
1366
1367             if (wParam & (MK_MBUTTON | MK_SHIFT))
1368                 button = MIDDLE_DRAG;
1369             else if (wParam & MK_LBUTTON)
1370                 button = LEFT_DRAG;
1371             else
1372                 button = RIGHT_DRAG;
1373             
1374             if (!midend_process_key(fe->me, (signed short)LOWORD(lParam),
1375                                     (signed short)HIWORD(lParam), button))
1376                 PostQuitMessage(0);
1377         }
1378         break;
1379       case WM_CHAR:
1380         if (!midend_process_key(fe->me, 0, 0, (unsigned char)wParam))
1381             PostQuitMessage(0);
1382         return 0;
1383       case WM_TIMER:
1384         if (fe->timer) {
1385             DWORD now = GetTickCount();
1386             float elapsed = (float) (now - fe->timer_last_tickcount) * 0.001F;
1387             midend_timer(fe->me, elapsed);
1388             fe->timer_last_tickcount = now;
1389         }
1390         return 0;
1391     }
1392
1393     return DefWindowProc(hwnd, message, wParam, lParam);
1394 }
1395
1396 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
1397 {
1398     MSG msg;
1399     char *error;
1400
1401     InitCommonControls();
1402
1403     if (!prev) {
1404         WNDCLASS wndclass;
1405
1406         wndclass.style = 0;
1407         wndclass.lpfnWndProc = WndProc;
1408         wndclass.cbClsExtra = 0;
1409         wndclass.cbWndExtra = 0;
1410         wndclass.hInstance = inst;
1411         wndclass.hIcon = LoadIcon(inst, IDI_APPLICATION);
1412         wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
1413         wndclass.hbrBackground = NULL;
1414         wndclass.lpszMenuName = NULL;
1415         wndclass.lpszClassName = thegame.name;
1416
1417         RegisterClass(&wndclass);
1418     }
1419
1420     while (*cmdline && isspace(*cmdline))
1421         cmdline++;
1422
1423     if (!new_window(inst, *cmdline ? cmdline : NULL, &error)) {
1424         char buf[128];
1425         sprintf(buf, "%.100s Error", thegame.name);
1426         MessageBox(NULL, error, buf, MB_OK|MB_ICONERROR);
1427         return 1;
1428     }
1429
1430     while (GetMessage(&msg, NULL, 0, 0)) {
1431         DispatchMessage(&msg);
1432     }
1433
1434     return msg.wParam;
1435 }