chiark / gitweb /
Dariusz Olszewski's changes to support compiling for PocketPC. This
[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 #ifndef NO_HTMLHELP
8 #include <htmlhelp.h>
9 #endif /* NO_HTMLHELP */
10
11 #ifdef _WIN32_WCE
12 #include <commdlg.h>
13 #include <aygshell.h>
14 #endif
15
16 #include <stdio.h>
17 #include <assert.h>
18 #include <ctype.h>
19 #include <stdarg.h>
20 #include <stdlib.h>
21 #include <limits.h>
22 #include <time.h>
23
24 #include "puzzles.h"
25
26 #ifdef _WIN32_WCE
27 #include "resource.h"
28 #endif
29
30 #define IDM_NEW       0x0010
31 #define IDM_RESTART   0x0020
32 #define IDM_UNDO      0x0030
33 #define IDM_REDO      0x0040
34 #define IDM_COPY      0x0050
35 #define IDM_SOLVE     0x0060
36 #define IDM_QUIT      0x0070
37 #define IDM_CONFIG    0x0080
38 #define IDM_DESC      0x0090
39 #define IDM_SEED      0x00A0
40 #define IDM_HELPC     0x00B0
41 #define IDM_GAMEHELP  0x00C0
42 #define IDM_ABOUT     0x00D0
43 #define IDM_SAVE      0x00E0
44 #define IDM_LOAD      0x00F0
45 #define IDM_PRINT     0x0100
46 #define IDM_PRESETS   0x0110
47
48 #define IDM_KEYEMUL   0x0400
49
50 #define HELP_FILE_NAME  "puzzles.hlp"
51 #define HELP_CNT_NAME   "puzzles.cnt"
52 #ifndef NO_HTMLHELP
53 #define CHM_FILE_NAME   "puzzles.chm"
54 #endif /* NO_HTMLHELP */
55
56 #ifndef NO_HTMLHELP
57 typedef HWND (CALLBACK *htmlhelp_t)(HWND, LPCSTR, UINT, DWORD);
58 static htmlhelp_t htmlhelp;
59 static HINSTANCE hh_dll;
60 #endif /* NO_HTMLHELP */
61 enum { NONE, HLP, CHM } help_type;
62 char *help_path;
63 const char *help_topic;
64 int help_has_contents;
65
66 #ifndef FILENAME_MAX
67 #define FILENAME_MAX    (260)
68 #endif
69
70 #ifndef HGDI_ERROR
71 #define HGDI_ERROR ((HANDLE)GDI_ERROR)
72 #endif
73
74 #ifdef _WIN32_WCE
75
76 /*
77  * Wrapper implementations of functions not supplied by the
78  * PocketPC API.
79  */
80
81 #define SHGetSubMenu(hWndMB,ID_MENU) (HMENU)SendMessage((hWndMB), SHCMBM_GETSUBMENU, (WPARAM)0, (LPARAM)ID_MENU)
82
83 #undef MessageBox
84
85 int MessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
86 {
87     TCHAR wText[2048];
88     TCHAR wCaption[2048];
89
90     MultiByteToWideChar (CP_ACP, 0, lpText,    -1, wText,    2048);
91     MultiByteToWideChar (CP_ACP, 0, lpCaption, -1, wCaption, 2048);
92
93     return MessageBoxW (hWnd, wText, wCaption, uType);
94 }
95
96 BOOL SetDlgItemTextA(HWND hDlg, int nIDDlgItem, LPCSTR lpString)
97 {
98     TCHAR wText[256];
99
100     MultiByteToWideChar (CP_ACP, 0, lpString, -1, wText, 256);
101     return SetDlgItemTextW(hDlg, nIDDlgItem, wText);
102 }
103
104 LPCSTR getenv(LPCSTR buf)
105 {
106     return NULL;
107 }
108
109 BOOL GetKeyboardState(PBYTE pb)
110 {
111   return FALSE;
112 }
113
114 static TCHAR wGameName[256];
115
116 #endif
117
118 #ifdef DEBUGGING
119 static FILE *debug_fp = NULL;
120 static HANDLE debug_hdl = INVALID_HANDLE_VALUE;
121 static int debug_got_console = 0;
122
123 void dputs(char *buf)
124 {
125     DWORD dw;
126
127     if (!debug_got_console) {
128         if (AllocConsole()) {
129             debug_got_console = 1;
130             debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE);
131         }
132     }
133     if (!debug_fp) {
134         debug_fp = fopen("debug.log", "w");
135     }
136
137     if (debug_hdl != INVALID_HANDLE_VALUE) {
138         WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL);
139     }
140     fputs(buf, debug_fp);
141     fflush(debug_fp);
142 }
143
144 void debug_printf(char *fmt, ...)
145 {
146     char buf[4096];
147     va_list ap;
148
149     va_start(ap, fmt);
150     _vsnprintf(buf, 4095, fmt, ap);
151     dputs(buf);
152     va_end(ap);
153 }
154 #endif
155
156 #ifndef _WIN32_WCE
157 #define WINFLAGS (WS_OVERLAPPEDWINDOW &~ \
158                       (WS_THICKFRAME | WS_MAXIMIZEBOX | WS_OVERLAPPED))
159 #else
160 #define WINFLAGS (WS_CAPTION | WS_SYSMENU)
161 #endif
162
163 static void new_game_size(frontend *fe);
164
165 struct font {
166     HFONT font;
167     int type;
168     int size;
169 };
170
171 struct cfg_aux {
172     int ctlid;
173 };
174
175 struct blitter {
176     HBITMAP bitmap;
177     frontend *fe;
178     int x, y, w, h;
179 };
180
181 enum { CFG_PRINT = CFG_FRONTEND_SPECIFIC };
182
183 struct frontend {
184     midend *me;
185     HWND hwnd, statusbar, cfgbox;
186 #ifdef _WIN32_WCE
187     HWND numpad;  /* window handle for the numeric pad */
188 #endif
189     HINSTANCE inst;
190     HBITMAP bitmap, prevbm;
191     RECT bitmapPosition;  /* game bitmap position within game window */
192     HDC hdc;
193     COLORREF *colours;
194     HBRUSH *brushes;
195     HPEN *pens;
196     HRGN clip;
197     UINT timer;
198     DWORD timer_last_tickcount;
199     int npresets;
200     game_params **presets;
201     struct font *fonts;
202     int nfonts, fontsize;
203     config_item *cfg;
204     struct cfg_aux *cfgaux;
205     int cfg_which, dlg_done;
206     HFONT cfgfont;
207     HBRUSH oldbr;
208     HPEN oldpen;
209     int help_running;
210     enum { DRAWING, PRINTING, NOTHING } drawstatus;
211     DOCINFO di;
212     int printcount, printw, printh, printsolns, printcurr, printcolour;
213     float printscale;
214     int printoffsetx, printoffsety;
215     float printpixelscale;
216     int fontstart;
217     int linewidth;
218     drawing *dr;
219 };
220
221 void fatal(char *fmt, ...)
222 {
223     char buf[2048];
224     va_list ap;
225
226     va_start(ap, fmt);
227     vsprintf(buf, fmt, ap);
228     va_end(ap);
229
230     MessageBox(NULL, buf, "Fatal error", MB_ICONEXCLAMATION | MB_OK);
231
232     exit(1);
233 }
234
235 char *geterrstr(void)
236 {
237     LPVOID lpMsgBuf;
238     DWORD dw = GetLastError();
239     char *ret;
240
241     FormatMessage(
242         FORMAT_MESSAGE_ALLOCATE_BUFFER | 
243         FORMAT_MESSAGE_FROM_SYSTEM,
244         NULL,
245         dw,
246         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
247         (LPTSTR) &lpMsgBuf,
248         0, NULL );
249
250     ret = dupstr(lpMsgBuf);
251
252     LocalFree(lpMsgBuf);
253
254     return ret;
255 }
256
257 void get_random_seed(void **randseed, int *randseedsize)
258 {
259     SYSTEMTIME *st = snew(SYSTEMTIME);
260
261     GetLocalTime(st);
262
263     *randseed = (void *)st;
264     *randseedsize = sizeof(SYSTEMTIME);
265 }
266
267 static void win_status_bar(void *handle, char *text)
268 {
269 #ifdef _WIN32_WCE
270     TCHAR wText[255];
271 #endif
272     frontend *fe = (frontend *)handle;
273
274 #ifdef _WIN32_WCE
275     MultiByteToWideChar (CP_ACP, 0, text, -1, wText, 255);
276     SendMessage(fe->statusbar, SB_SETTEXT,
277                 (WPARAM) 255 | SBT_NOBORDERS,
278                 (LPARAM) wText);
279 #else
280     SetWindowText(fe->statusbar, text);
281 #endif
282 }
283
284 static blitter *win_blitter_new(void *handle, int w, int h)
285 {
286     blitter *bl = snew(blitter);
287
288     memset(bl, 0, sizeof(blitter));
289     bl->w = w;
290     bl->h = h;
291     bl->bitmap = 0;
292
293     return bl;
294 }
295
296 static void win_blitter_free(void *handle, blitter *bl)
297 {
298     if (bl->bitmap) DeleteObject(bl->bitmap);
299     sfree(bl);
300 }
301
302 static void blitter_mkbitmap(frontend *fe, blitter *bl)
303 {
304     HDC hdc = GetDC(fe->hwnd);
305     bl->bitmap = CreateCompatibleBitmap(hdc, bl->w, bl->h);
306     ReleaseDC(fe->hwnd, hdc);
307 }
308
309 /* BitBlt(dstDC, dstX, dstY, dstW, dstH, srcDC, srcX, srcY, dType) */
310
311 static void win_blitter_save(void *handle, blitter *bl, int x, int y)
312 {
313     frontend *fe = (frontend *)handle;
314     HDC hdc_win, hdc_blit;
315     HBITMAP prev_blit;
316
317     assert(fe->drawstatus == DRAWING);
318
319     if (!bl->bitmap) blitter_mkbitmap(fe, bl);
320
321     bl->x = x; bl->y = y;
322
323     hdc_win = GetDC(fe->hwnd);
324     hdc_blit = CreateCompatibleDC(hdc_win);
325     if (!hdc_blit) fatal("hdc_blit failed: 0x%x", GetLastError());
326
327     prev_blit = SelectObject(hdc_blit, bl->bitmap);
328     if (prev_blit == NULL || prev_blit == HGDI_ERROR)
329         fatal("SelectObject for hdc_main failed: 0x%x", GetLastError());
330
331     if (!BitBlt(hdc_blit, 0, 0, bl->w, bl->h,
332                 fe->hdc, x, y, SRCCOPY))
333         fatal("BitBlt failed: 0x%x", GetLastError());
334
335     SelectObject(hdc_blit, prev_blit);
336     DeleteDC(hdc_blit);
337     ReleaseDC(fe->hwnd, hdc_win);
338 }
339
340 static void win_blitter_load(void *handle, blitter *bl, int x, int y)
341 {
342     frontend *fe = (frontend *)handle;
343     HDC hdc_win, hdc_blit;
344     HBITMAP prev_blit;
345
346     assert(fe->drawstatus == DRAWING);
347
348     assert(bl->bitmap); /* we should always have saved before loading */
349
350     if (x == BLITTER_FROMSAVED) x = bl->x;
351     if (y == BLITTER_FROMSAVED) y = bl->y;
352
353     hdc_win = GetDC(fe->hwnd);
354     hdc_blit = CreateCompatibleDC(hdc_win);
355
356     prev_blit = SelectObject(hdc_blit, bl->bitmap);
357
358     BitBlt(fe->hdc, x, y, bl->w, bl->h,
359            hdc_blit, 0, 0, SRCCOPY);
360
361     SelectObject(hdc_blit, prev_blit);
362     DeleteDC(hdc_blit);
363     ReleaseDC(fe->hwnd, hdc_win);
364 }
365
366 void frontend_default_colour(frontend *fe, float *output)
367 {
368     DWORD c = GetSysColor(COLOR_MENU); /* ick */
369
370     output[0] = (float)(GetRValue(c) / 255.0);
371     output[1] = (float)(GetGValue(c) / 255.0);
372     output[2] = (float)(GetBValue(c) / 255.0);
373 }
374
375 static POINT win_transform_point(frontend *fe, int x, int y)
376 {
377     POINT ret;
378
379     assert(fe->drawstatus != NOTHING);
380
381     if (fe->drawstatus == PRINTING) {
382         ret.x = (int)(fe->printoffsetx + fe->printpixelscale * x);
383         ret.y = (int)(fe->printoffsety + fe->printpixelscale * y);
384     } else {
385         ret.x = x;
386         ret.y = y;
387     }
388
389     return ret;
390 }
391
392 static void win_text_colour(frontend *fe, int colour)
393 {
394     assert(fe->drawstatus != NOTHING);
395
396     if (fe->drawstatus == PRINTING) {
397         int hatch;
398         float r, g, b;
399         print_get_colour(fe->dr, colour, &hatch, &r, &g, &b);
400         if (fe->printcolour)
401             SetTextColor(fe->hdc, RGB(r * 255, g * 255, b * 255));
402         else
403             SetTextColor(fe->hdc,
404                          hatch == HATCH_CLEAR ? RGB(255,255,255) : RGB(0,0,0));
405     } else {
406         SetTextColor(fe->hdc, fe->colours[colour]);
407     }
408 }
409
410 static void win_set_brush(frontend *fe, int colour)
411 {
412     HBRUSH br;
413     assert(fe->drawstatus != NOTHING);
414
415     if (fe->drawstatus == PRINTING) {
416         int hatch;
417         float r, g, b;
418         print_get_colour(fe->dr, colour, &hatch, &r, &g, &b);
419
420         if (fe->printcolour) {
421             br = CreateSolidBrush(RGB(r * 255, g * 255, b * 255));
422         } else if (hatch == HATCH_SOLID) {
423             br = CreateSolidBrush(RGB(0,0,0));
424         } else if (hatch == HATCH_CLEAR) {
425             br = CreateSolidBrush(RGB(255,255,255));
426         } else {
427 #ifdef _WIN32_WCE
428             /*
429              * This is only ever required during printing, and the
430              * PocketPC port doesn't support printing.
431              */
432             fatal("CreateHatchBrush not supported");
433 #else
434             br = CreateHatchBrush(hatch == HATCH_BACKSLASH ? HS_FDIAGONAL :
435                                   hatch == HATCH_SLASH ? HS_BDIAGONAL :
436                                   hatch == HATCH_HORIZ ? HS_HORIZONTAL :
437                                   hatch == HATCH_VERT ? HS_VERTICAL :
438                                   hatch == HATCH_PLUS ? HS_CROSS :
439                                   /* hatch == HATCH_X ? */ HS_DIAGCROSS,
440                                   RGB(0,0,0));
441 #endif
442         }
443     } else {
444         br = fe->brushes[colour];
445     }
446     fe->oldbr = SelectObject(fe->hdc, br);
447 }
448
449 static void win_reset_brush(frontend *fe)
450 {
451     HBRUSH br;
452
453     assert(fe->drawstatus != NOTHING);
454
455     br = SelectObject(fe->hdc, fe->oldbr);
456     if (fe->drawstatus == PRINTING)
457         DeleteObject(br);
458 }
459
460 static void win_set_pen(frontend *fe, int colour, int thin)
461 {
462     HPEN pen;
463     assert(fe->drawstatus != NOTHING);
464
465     if (fe->drawstatus == PRINTING) {
466         int hatch;
467         float r, g, b;
468         int width = thin ? 0 : fe->linewidth;
469
470         print_get_colour(fe->dr, colour, &hatch, &r, &g, &b);
471         if (fe->printcolour)
472             pen = CreatePen(PS_SOLID, width,
473                             RGB(r * 255, g * 255, b * 255));
474         else if (hatch == HATCH_SOLID)
475             pen = CreatePen(PS_SOLID, width, RGB(0, 0, 0));
476         else if (hatch == HATCH_CLEAR)
477             pen = CreatePen(PS_SOLID, width, RGB(255,255,255));
478         else {
479             assert(!"This shouldn't happen");
480             pen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
481         }
482     } else {
483         pen = fe->pens[colour];
484     }
485     fe->oldpen = SelectObject(fe->hdc, pen);
486 }
487
488 static void win_reset_pen(frontend *fe)
489 {
490     HPEN pen;
491
492     assert(fe->drawstatus != NOTHING);
493
494     pen = SelectObject(fe->hdc, fe->oldpen);
495     if (fe->drawstatus == PRINTING)
496         DeleteObject(pen);
497 }
498
499 static void win_clip(void *handle, int x, int y, int w, int h)
500 {
501     frontend *fe = (frontend *)handle;
502     POINT p, q;
503
504     if (fe->drawstatus == NOTHING)
505         return;
506
507     p = win_transform_point(fe, x, y);
508     q = win_transform_point(fe, x+w, y+h);
509     IntersectClipRect(fe->hdc, p.x, p.y, q.x, q.y);
510 }
511
512 static void win_unclip(void *handle)
513 {
514     frontend *fe = (frontend *)handle;
515
516     if (fe->drawstatus == NOTHING)
517         return;
518
519     SelectClipRgn(fe->hdc, NULL);
520 }
521
522 static void win_draw_text(void *handle, int x, int y, int fonttype,
523                           int fontsize, int align, int colour, char *text)
524 {
525     frontend *fe = (frontend *)handle;
526     POINT xy;
527     int i;
528     LOGFONT lf;
529
530     if (fe->drawstatus == NOTHING)
531         return;
532
533     if (fe->drawstatus == PRINTING)
534         fontsize = (int)(fontsize * fe->printpixelscale);
535
536     xy = win_transform_point(fe, x, y);
537
538     /*
539      * Find or create the font.
540      */
541     for (i = fe->fontstart; i < fe->nfonts; i++)
542         if (fe->fonts[i].type == fonttype && fe->fonts[i].size == fontsize)
543             break;
544
545     if (i == fe->nfonts) {
546         if (fe->fontsize <= fe->nfonts) {
547             fe->fontsize = fe->nfonts + 10;
548             fe->fonts = sresize(fe->fonts, fe->fontsize, struct font);
549         }
550
551         fe->nfonts++;
552
553         fe->fonts[i].type = fonttype;
554         fe->fonts[i].size = fontsize;
555
556         memset (&lf, 0, sizeof(LOGFONT));
557         lf.lfHeight = -fontsize;
558         lf.lfWeight = (fe->drawstatus == PRINTING ? 0 : FW_BOLD);
559         lf.lfCharSet = DEFAULT_CHARSET;
560         lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
561         lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
562         lf.lfQuality = DEFAULT_QUALITY;
563         lf.lfPitchAndFamily = (fonttype == FONT_FIXED ?
564                                FIXED_PITCH | FF_DONTCARE :
565                                VARIABLE_PITCH | FF_SWISS);
566 #ifdef _WIN32_WCE
567         wcscpy(lf.lfFaceName, TEXT("Tahoma"));
568 #endif
569
570         fe->fonts[i].font = CreateFontIndirect(&lf);
571     }
572
573     /*
574      * Position and draw the text.
575      */
576     {
577         HFONT oldfont;
578         TEXTMETRIC tm;
579         SIZE size;
580 #ifdef _WIN32_WCE
581         TCHAR wText[256];
582         MultiByteToWideChar (CP_ACP, 0, text, -1, wText, 256);
583 #endif
584
585         oldfont = SelectObject(fe->hdc, fe->fonts[i].font);
586         if (GetTextMetrics(fe->hdc, &tm)) {
587             if (align & ALIGN_VCENTRE)
588                 xy.y -= (tm.tmAscent+tm.tmDescent)/2;
589             else
590                 xy.y -= tm.tmAscent;
591         }
592 #ifndef _WIN32_WCE
593         if (GetTextExtentPoint32(fe->hdc, text, strlen(text), &size)) {
594 #else
595         if (GetTextExtentPoint32(fe->hdc, wText, wcslen(wText), &size)) {
596 #endif
597             if (align & ALIGN_HCENTRE)
598                 xy.x -= size.cx / 2;
599             else if (align & ALIGN_HRIGHT)
600                 xy.x -= size.cx;
601         }
602         SetBkMode(fe->hdc, TRANSPARENT);
603         win_text_colour(fe, colour);
604 #ifndef _WIN32_WCE
605         TextOut(fe->hdc, xy.x, xy.y, text, strlen(text));
606 #else
607         ExtTextOut(fe->hdc, xy.x, xy.y, 0, NULL, wText, wcslen(wText), NULL);
608 #endif
609         SelectObject(fe->hdc, oldfont);
610     }
611 }
612
613 static void win_draw_rect(void *handle, int x, int y, int w, int h, int colour)
614 {
615     frontend *fe = (frontend *)handle;
616     POINT p, q;
617
618     if (fe->drawstatus == NOTHING)
619         return;
620
621     if (fe->drawstatus == DRAWING && w == 1 && h == 1) {
622         /*
623          * Rectangle() appears to get uppity if asked to draw a 1x1
624          * rectangle, presumably on the grounds that that's beneath
625          * its dignity and you ought to be using SetPixel instead.
626          * So I will.
627          */
628         SetPixel(fe->hdc, x, y, fe->colours[colour]);
629     } else {
630         win_set_brush(fe, colour);
631         win_set_pen(fe, colour, TRUE);
632         p = win_transform_point(fe, x, y);
633         q = win_transform_point(fe, x+w, y+h);
634         Rectangle(fe->hdc, p.x, p.y, q.x, q.y);
635         win_reset_brush(fe);
636         win_reset_pen(fe);
637     }
638 }
639
640 static void win_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour)
641 {
642     frontend *fe = (frontend *)handle;
643     POINT pp[2];
644
645     if (fe->drawstatus == NOTHING)
646         return;
647
648     win_set_pen(fe, colour, FALSE);
649     pp[0] = win_transform_point(fe, x1, y1);
650     pp[1] = win_transform_point(fe, x2, y2);
651     Polyline(fe->hdc, pp, 2);
652     if (fe->drawstatus == DRAWING)
653         SetPixel(fe->hdc, pp[1].x, pp[1].y, fe->colours[colour]);
654     win_reset_pen(fe);
655 }
656
657 static void win_draw_circle(void *handle, int cx, int cy, int radius,
658                             int fillcolour, int outlinecolour)
659 {
660     frontend *fe = (frontend *)handle;
661     POINT p, q;
662
663     assert(outlinecolour >= 0);
664
665     if (fe->drawstatus == NOTHING)
666         return;
667
668     if (fillcolour >= 0)
669         win_set_brush(fe, fillcolour);
670     else
671         fe->oldbr = SelectObject(fe->hdc, GetStockObject(NULL_BRUSH));
672
673     win_set_pen(fe, outlinecolour, FALSE);
674     p = win_transform_point(fe, cx - radius, cy - radius);
675     q = win_transform_point(fe, cx + radius, cy + radius);
676     Ellipse(fe->hdc, p.x, p.y, q.x+1, q.y+1);
677     win_reset_brush(fe);
678     win_reset_pen(fe);
679 }
680
681 static void win_draw_polygon(void *handle, int *coords, int npoints,
682                              int fillcolour, int outlinecolour)
683 {
684     frontend *fe = (frontend *)handle;
685     POINT *pts;
686     int i;
687
688     if (fe->drawstatus == NOTHING)
689         return;
690
691     pts = snewn(npoints+1, POINT);
692
693     for (i = 0; i <= npoints; i++) {
694         int j = (i < npoints ? i : 0);
695         pts[i] = win_transform_point(fe, coords[j*2], coords[j*2+1]);
696     }
697
698     assert(outlinecolour >= 0);
699
700     if (fillcolour >= 0) {
701         win_set_brush(fe, fillcolour);
702         win_set_pen(fe, outlinecolour, FALSE);
703         Polygon(fe->hdc, pts, npoints);
704         win_reset_brush(fe);
705         win_reset_pen(fe);
706     } else {
707         win_set_pen(fe, outlinecolour, FALSE);
708         Polyline(fe->hdc, pts, npoints+1);
709         win_reset_pen(fe);
710     }
711
712     sfree(pts);
713 }
714
715 static void win_start_draw(void *handle)
716 {
717     frontend *fe = (frontend *)handle;
718     HDC hdc_win;
719
720     assert(fe->drawstatus == NOTHING);
721
722     hdc_win = GetDC(fe->hwnd);
723     fe->hdc = CreateCompatibleDC(hdc_win);
724     fe->prevbm = SelectObject(fe->hdc, fe->bitmap);
725     ReleaseDC(fe->hwnd, hdc_win);
726     fe->clip = NULL;
727 #ifndef _WIN32_WCE
728     SetMapMode(fe->hdc, MM_TEXT);
729 #endif
730     fe->drawstatus = DRAWING;
731 }
732
733 static void win_draw_update(void *handle, int x, int y, int w, int h)
734 {
735     frontend *fe = (frontend *)handle;
736     RECT r;
737
738     if (fe->drawstatus != DRAWING)
739         return;
740
741     r.left = x;
742     r.top = y;
743     r.right = x + w;
744     r.bottom = y + h;
745
746     OffsetRect(&r, fe->bitmapPosition.left, fe->bitmapPosition.top);
747     InvalidateRect(fe->hwnd, &r, FALSE);
748 }
749
750 static void win_end_draw(void *handle)
751 {
752     frontend *fe = (frontend *)handle;
753     assert(fe->drawstatus == DRAWING);
754     SelectObject(fe->hdc, fe->prevbm);
755     DeleteDC(fe->hdc);
756     if (fe->clip) {
757         DeleteObject(fe->clip);
758         fe->clip = NULL;
759     }
760     fe->drawstatus = NOTHING;
761 }
762
763 static void win_line_width(void *handle, float width)
764 {
765     frontend *fe = (frontend *)handle;
766
767     assert(fe->drawstatus != DRAWING);
768     if (fe->drawstatus == NOTHING)
769         return;
770
771     fe->linewidth = (int)(width * fe->printpixelscale);
772 }
773
774 static void win_begin_doc(void *handle, int pages)
775 {
776     frontend *fe = (frontend *)handle;
777
778     assert(fe->drawstatus != DRAWING);
779     if (fe->drawstatus == NOTHING)
780         return;
781
782     if (StartDoc(fe->hdc, &fe->di) <= 0) {
783         char *e = geterrstr();
784         MessageBox(fe->hwnd, e, "Error starting to print",
785                    MB_ICONERROR | MB_OK);
786         sfree(e);
787         fe->drawstatus = NOTHING;
788     }
789
790     /*
791      * Push a marker on the font stack so that we won't use the
792      * same fonts for printing and drawing. (This is because
793      * drawing seems to look generally better in bold, but printing
794      * is better not in bold.)
795      */
796     fe->fontstart = fe->nfonts;
797 }
798
799 static void win_begin_page(void *handle, int number)
800 {
801     frontend *fe = (frontend *)handle;
802
803     assert(fe->drawstatus != DRAWING);
804     if (fe->drawstatus == NOTHING)
805         return;
806
807     if (StartPage(fe->hdc) <= 0) {
808         char *e = geterrstr();
809         MessageBox(fe->hwnd, e, "Error starting a page",
810                    MB_ICONERROR | MB_OK);
811         sfree(e);
812         fe->drawstatus = NOTHING;
813     }
814 }
815
816 static void win_begin_puzzle(void *handle, float xm, float xc,
817                              float ym, float yc, int pw, int ph, float wmm)
818 {
819     frontend *fe = (frontend *)handle;
820     int ppw, pph, pox, poy;
821     float mmpw, mmph, mmox, mmoy;
822     float scale;
823
824     assert(fe->drawstatus != DRAWING);
825     if (fe->drawstatus == NOTHING)
826         return;
827
828     ppw = GetDeviceCaps(fe->hdc, HORZRES);
829     pph = GetDeviceCaps(fe->hdc, VERTRES);
830     mmpw = (float)GetDeviceCaps(fe->hdc, HORZSIZE);
831     mmph = (float)GetDeviceCaps(fe->hdc, VERTSIZE);
832
833     /*
834      * Compute the puzzle's position on the logical page.
835      */
836     mmox = xm * mmpw + xc;
837     mmoy = ym * mmph + yc;
838
839     /*
840      * Work out what that comes to in pixels.
841      */
842     pox = (int)(mmox * (float)ppw / mmpw);
843     poy = (int)(mmoy * (float)ppw / mmpw);
844
845     /*
846      * And determine the scale.
847      * 
848      * I need a scale such that the maximum puzzle-coordinate
849      * extent of the rectangle (pw * scale) is equal to the pixel
850      * equivalent of the puzzle's millimetre width (wmm * ppw /
851      * mmpw).
852      */
853     scale = (wmm * ppw) / (mmpw * pw);
854
855     /*
856      * Now store pox, poy and scale for use in the main drawing
857      * functions.
858      */
859     fe->printoffsetx = pox;
860     fe->printoffsety = poy;
861     fe->printpixelscale = scale;
862
863     fe->linewidth = 1;
864 }
865
866 static void win_end_puzzle(void *handle)
867 {
868     /* Nothing needs to be done here. */
869 }
870
871 static void win_end_page(void *handle, int number)
872 {
873     frontend *fe = (frontend *)handle;
874
875     assert(fe->drawstatus != DRAWING);
876
877     if (fe->drawstatus == NOTHING)
878         return;
879
880     if (EndPage(fe->hdc) <= 0) {
881         char *e = geterrstr();
882         MessageBox(fe->hwnd, e, "Error finishing a page",
883                    MB_ICONERROR | MB_OK);
884         sfree(e);
885         fe->drawstatus = NOTHING;
886     }
887 }
888
889 static void win_end_doc(void *handle)
890 {
891     frontend *fe = (frontend *)handle;
892
893     assert(fe->drawstatus != DRAWING);
894
895     /*
896      * Free all the fonts created since we began printing.
897      */
898     while (fe->nfonts > fe->fontstart) {
899         fe->nfonts--;
900         DeleteObject(fe->fonts[fe->nfonts].font);
901     }
902     fe->fontstart = 0;
903
904     /*
905      * The MSDN web site sample code doesn't bother to call EndDoc
906      * if an error occurs half way through printing. I expect doing
907      * so would cause the erroneous document to actually be
908      * printed, or something equally undesirable.
909      */
910     if (fe->drawstatus == NOTHING)
911         return;
912
913     if (EndDoc(fe->hdc) <= 0) {
914         char *e = geterrstr();
915         MessageBox(fe->hwnd, e, "Error finishing printing",
916                    MB_ICONERROR | MB_OK);
917         sfree(e);
918         fe->drawstatus = NOTHING;
919     }
920 }
921
922 const struct drawing_api win_drawing = {
923     win_draw_text,
924     win_draw_rect,
925     win_draw_line,
926     win_draw_polygon,
927     win_draw_circle,
928     win_draw_update,
929     win_clip,
930     win_unclip,
931     win_start_draw,
932     win_end_draw,
933     win_status_bar,
934     win_blitter_new,
935     win_blitter_free,
936     win_blitter_save,
937     win_blitter_load,
938     win_begin_doc,
939     win_begin_page,
940     win_begin_puzzle,
941     win_end_puzzle,
942     win_end_page,
943     win_end_doc,
944     win_line_width,
945 };
946
947 void print(frontend *fe)
948 {
949 #ifndef _WIN32_WCE
950     PRINTDLG pd;
951     char doctitle[256];
952     document *doc;
953     midend *nme = NULL;  /* non-interactive midend for bulk puzzle generation */
954     int i;
955     char *err = NULL;
956
957     /*
958      * Create our document structure and fill it up with puzzles.
959      */
960     doc = document_new(fe->printw, fe->printh, fe->printscale / 100.0F);
961     for (i = 0; i < fe->printcount; i++) {
962         if (i == 0 && fe->printcurr) {
963             err = midend_print_puzzle(fe->me, doc, fe->printsolns);
964         } else {
965             if (!nme) {
966                 game_params *params;
967
968                 nme = midend_new(NULL, &thegame, NULL, NULL);
969
970                 /*
971                  * Set the non-interactive mid-end to have the same
972                  * parameters as the standard one.
973                  */
974                 params = midend_get_params(fe->me);
975                 midend_set_params(nme, params);
976                 thegame.free_params(params);
977             }
978
979             midend_new_game(nme);
980             err = midend_print_puzzle(nme, doc, fe->printsolns);
981         }
982         if (err)
983             break;
984     }
985     if (nme)
986         midend_free(nme);
987
988     if (err) {
989         MessageBox(fe->hwnd, err, "Error preparing puzzles for printing",
990                    MB_ICONERROR | MB_OK);
991         document_free(doc);
992         return;
993     }
994
995     memset(&pd, 0, sizeof(pd));
996     pd.lStructSize = sizeof(pd);
997     pd.hwndOwner = fe->hwnd;
998     pd.hDevMode = NULL;
999     pd.hDevNames = NULL;
1000     pd.Flags = PD_USEDEVMODECOPIESANDCOLLATE | PD_RETURNDC |
1001         PD_NOPAGENUMS | PD_NOSELECTION;
1002     pd.nCopies = 1;
1003     pd.nFromPage = pd.nToPage = 0xFFFF;
1004     pd.nMinPage = pd.nMaxPage = 1;
1005
1006     if (!PrintDlg(&pd)) {
1007         document_free(doc);
1008         return;
1009     }
1010
1011     /*
1012      * Now pd.hDC is a device context for the printer.
1013      */
1014
1015     /*
1016      * FIXME: IWBNI we put up an Abort box here.
1017      */
1018
1019     memset(&fe->di, 0, sizeof(fe->di));
1020     fe->di.cbSize = sizeof(fe->di);
1021     sprintf(doctitle, "Printed puzzles from %s (from Simon Tatham's"
1022             " Portable Puzzle Collection)", thegame.name);
1023     fe->di.lpszDocName = doctitle;
1024     fe->di.lpszOutput = NULL;
1025     fe->di.lpszDatatype = NULL;
1026     fe->di.fwType = 0;
1027
1028     fe->drawstatus = PRINTING;
1029     fe->hdc = pd.hDC;
1030
1031     fe->dr = drawing_new(&win_drawing, NULL, fe);
1032     document_print(doc, fe->dr);
1033     drawing_free(fe->dr);
1034     fe->dr = NULL;
1035
1036     fe->drawstatus = NOTHING;
1037
1038     DeleteDC(pd.hDC);
1039     document_free(doc);
1040 #endif
1041 }
1042
1043 void deactivate_timer(frontend *fe)
1044 {
1045     if (!fe)
1046         return;                        /* for non-interactive midend */
1047     if (fe->hwnd) KillTimer(fe->hwnd, fe->timer);
1048     fe->timer = 0;
1049 }
1050
1051 void activate_timer(frontend *fe)
1052 {
1053     if (!fe)
1054         return;                        /* for non-interactive midend */
1055     if (!fe->timer) {
1056         fe->timer = SetTimer(fe->hwnd, 1, 20, NULL);
1057         fe->timer_last_tickcount = GetTickCount();
1058     }
1059 }
1060
1061 void write_clip(HWND hwnd, char *data)
1062 {
1063     HGLOBAL clipdata;
1064     int len, i, j;
1065     char *data2;
1066     void *lock;
1067
1068     /*
1069      * Windows expects CRLF in the clipboard, so we must convert
1070      * any \n that has come out of the puzzle backend.
1071      */
1072     len = 0;
1073     for (i = 0; data[i]; i++) {
1074         if (data[i] == '\n')
1075             len++;
1076         len++;
1077     }
1078     data2 = snewn(len+1, char);
1079     j = 0;
1080     for (i = 0; data[i]; i++) {
1081         if (data[i] == '\n')
1082             data2[j++] = '\r';
1083         data2[j++] = data[i];
1084     }
1085     assert(j == len);
1086     data2[j] = '\0';
1087
1088     clipdata = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, len + 1);
1089     if (!clipdata)
1090         return;
1091     lock = GlobalLock(clipdata);
1092     if (!lock)
1093         return;
1094     memcpy(lock, data2, len);
1095     ((unsigned char *) lock)[len] = 0;
1096     GlobalUnlock(clipdata);
1097
1098     if (OpenClipboard(hwnd)) {
1099         EmptyClipboard();
1100         SetClipboardData(CF_TEXT, clipdata);
1101         CloseClipboard();
1102     } else
1103         GlobalFree(clipdata);
1104
1105     sfree(data2);
1106 }
1107
1108 /*
1109  * Set up Help and see if we can find a help file.
1110  */
1111 static void init_help(void)
1112 {
1113 #ifndef _WIN32_WCE
1114     char b[2048], *p, *q, *r;
1115     FILE *fp;
1116
1117     /*
1118      * Find the executable file path, so we can look alongside
1119      * it for help files. Trim the filename off the end.
1120      */
1121     GetModuleFileName(NULL, b, sizeof(b) - 1);
1122     r = b;
1123     p = strrchr(b, '\\');
1124     if (p && p >= r) r = p+1;
1125     q = strrchr(b, ':');
1126     if (q && q >= r) r = q+1;
1127
1128 #ifndef NO_HTMLHELP
1129     /*
1130      * Try HTML Help first.
1131      */
1132     strcpy(r, CHM_FILE_NAME);
1133     if ( (fp = fopen(b, "r")) != NULL) {
1134         fclose(fp);
1135
1136         /*
1137          * We have a .CHM. See if we can use it.
1138          */
1139         hh_dll = LoadLibrary("hhctrl.ocx");
1140         if (hh_dll) {
1141             htmlhelp = (htmlhelp_t)GetProcAddress(hh_dll, "HtmlHelpA");
1142             if (!htmlhelp)
1143                 FreeLibrary(hh_dll);
1144         }
1145         if (htmlhelp) {
1146             help_path = dupstr(b);
1147             help_type = CHM;
1148             help_topic = thegame.htmlhelp_topic;
1149             return;
1150         }
1151     }
1152 #endif /* NO_HTMLHELP */
1153
1154     /*
1155      * Now try old-style .HLP.
1156      */
1157     strcpy(r, HELP_FILE_NAME);
1158     if ( (fp = fopen(b, "r")) != NULL) {
1159         fclose(fp);
1160
1161         help_path = dupstr(b);
1162         help_type = HLP;
1163
1164         help_topic = thegame.winhelp_topic;
1165
1166         /*
1167          * See if there's a .CNT file alongside it.
1168          */
1169         strcpy(r, HELP_CNT_NAME);
1170         if ( (fp = fopen(b, "r")) != NULL) {
1171             fclose(fp);
1172             help_has_contents = TRUE;
1173         } else
1174             help_has_contents = FALSE;
1175
1176         return;
1177     }
1178
1179     help_type = NONE;          /* didn't find any */
1180 #endif
1181 }
1182
1183 #ifndef _WIN32_WCE
1184
1185 /*
1186  * Start Help.
1187  */
1188 static void start_help(frontend *fe, const char *topic)
1189 {
1190     char *str = NULL;
1191     int cmd;
1192
1193     switch (help_type) {
1194       case HLP:
1195         assert(help_path);
1196         if (topic) {
1197             str = snewn(10+strlen(topic), char);
1198             sprintf(str, "JI(`',`%s')", topic);
1199             cmd = HELP_COMMAND;
1200         } else if (help_has_contents) {
1201             cmd = HELP_FINDER;
1202         } else {
1203             cmd = HELP_CONTENTS;
1204         }
1205         WinHelp(fe->hwnd, help_path, cmd, (DWORD)str);
1206         fe->help_running = TRUE;
1207         break;
1208       case CHM:
1209 #ifndef NO_HTMLHELP
1210         assert(help_path);
1211         assert(htmlhelp);
1212         if (topic) {
1213             str = snewn(20 + strlen(topic) + strlen(help_path), char);
1214             sprintf(str, "%s::/%s.html>main", help_path, topic);
1215         } else {
1216             str = dupstr(help_path);
1217         }
1218         htmlhelp(fe->hwnd, str, HH_DISPLAY_TOPIC, 0);
1219         fe->help_running = TRUE;
1220         break;
1221 #endif /* NO_HTMLHELP */
1222       case NONE:
1223         assert(!"This shouldn't happen");
1224         break;
1225     }
1226
1227     sfree(str);
1228 }
1229
1230 /*
1231  * Stop Help on window cleanup.
1232  */
1233 static void stop_help(frontend *fe)
1234 {
1235     if (fe->help_running) {
1236         switch (help_type) {
1237           case HLP:
1238             WinHelp(fe->hwnd, help_path, HELP_QUIT, 0);
1239             break;
1240           case CHM:
1241 #ifndef NO_HTMLHELP
1242             assert(htmlhelp);
1243             htmlhelp(NULL, NULL, HH_CLOSE_ALL, 0);
1244             break;
1245 #endif /* NO_HTMLHELP */
1246           case NONE:
1247             assert(!"This shouldn't happen");
1248             break;
1249         }
1250         fe->help_running = FALSE;
1251     }
1252 }
1253
1254 #endif
1255
1256 /*
1257  * Terminate Help on process exit.
1258  */
1259 static void cleanup_help(void)
1260 {
1261     /* Nothing to do currently.
1262      * (If we were running HTML Help single-threaded, this is where we'd
1263      * call HH_UNINITIALIZE.) */
1264 }
1265
1266 static void check_window_size(frontend *fe, int *px, int *py)
1267 {
1268     RECT r;
1269     int x, y, sy;
1270
1271     if (fe->statusbar) {
1272         RECT sr;
1273         GetWindowRect(fe->statusbar, &sr);
1274         sy = sr.bottom - sr.top;
1275     } else {
1276         sy = 0;
1277     }
1278
1279     /*
1280      * See if we actually got the window size we wanted, and adjust
1281      * the puzzle size if not.
1282      */
1283     GetClientRect(fe->hwnd, &r);
1284     x = r.right - r.left;
1285     y = r.bottom - r.top - sy;
1286     midend_size(fe->me, &x, &y, FALSE);
1287     if (x != r.right - r.left || y != r.bottom - r.top) {
1288         /*
1289          * Resize the window, now we know what size we _really_
1290          * want it to be.
1291          */
1292         r.left = r.top = 0;
1293         r.right = x;
1294         r.bottom = y + sy;
1295         AdjustWindowRectEx(&r, WINFLAGS, TRUE, 0);
1296 #ifndef _WIN32_WCE
1297         SetWindowPos(fe->hwnd, NULL, 0, 0, r.right - r.left, r.bottom - r.top,
1298                      SWP_NOMOVE | SWP_NOZORDER);
1299 #endif
1300     }
1301
1302     if (fe->statusbar) {
1303         GetClientRect(fe->hwnd, &r);
1304 #ifndef _WIN32_WCE
1305         SetWindowPos(fe->statusbar, NULL, 0, r.bottom-r.top-sy, r.right-r.left,
1306                      sy, SWP_NOZORDER);
1307 #endif
1308     }
1309
1310     *px = x;
1311     *py = y;
1312 }
1313
1314 static void get_max_puzzle_size(frontend *fe, int *x, int *y)
1315 {
1316     RECT r, sr;
1317
1318     if (SystemParametersInfo(SPI_GETWORKAREA, 0, &sr, FALSE)) {
1319         *x = sr.right - sr.left;
1320         *y = sr.bottom - sr.top;
1321         r.left = 100;
1322         r.right = 200;
1323         r.top = 100;
1324         r.bottom = 200;
1325         AdjustWindowRectEx(&r, WINFLAGS, TRUE, 0);
1326         *x -= r.right - r.left - 100;
1327         *y -= r.bottom - r.top - 100;
1328     } else {
1329         *x = *y = INT_MAX;
1330     }
1331
1332     if (fe->statusbar != NULL) {
1333         GetWindowRect(fe->statusbar, &sr);
1334         *y -= sr.bottom - sr.top;
1335     }
1336 }
1337
1338 #ifdef _WIN32_WCE
1339 /* Toolbar buttons on the numeric pad */
1340 static TBBUTTON tbNumpadButtons[] =
1341 {
1342     {0, IDM_KEYEMUL + '1', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
1343     {1, IDM_KEYEMUL + '2', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
1344     {2, IDM_KEYEMUL + '3', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
1345     {3, IDM_KEYEMUL + '4', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
1346     {4, IDM_KEYEMUL + '5', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
1347     {5, IDM_KEYEMUL + '6', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
1348     {6, IDM_KEYEMUL + '7', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
1349     {7, IDM_KEYEMUL + '8', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
1350     {8, IDM_KEYEMUL + '9', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1},
1351     {9, IDM_KEYEMUL + ' ', TBSTATE_ENABLED, TBSTYLE_BUTTON,  0, -1}
1352 };
1353 #endif
1354
1355 static frontend *new_window(HINSTANCE inst, char *game_id, char **error)
1356 {
1357     frontend *fe;
1358     int x, y;
1359     RECT r;
1360
1361     fe = snew(frontend);
1362
1363     fe->me = midend_new(fe, &thegame, &win_drawing, fe);
1364
1365     if (game_id) {
1366         *error = midend_game_id(fe->me, game_id);
1367         if (*error) {
1368             midend_free(fe->me);
1369             sfree(fe);
1370             return NULL;
1371         }
1372     }
1373
1374     fe->inst = inst;
1375
1376     fe->timer = 0;
1377     fe->hwnd = NULL;
1378
1379     fe->help_running = FALSE;
1380
1381     fe->drawstatus = NOTHING;
1382     fe->dr = NULL;
1383     fe->fontstart = 0;
1384
1385     midend_new_game(fe->me);
1386
1387     fe->fonts = NULL;
1388     fe->nfonts = fe->fontsize = 0;
1389
1390     {
1391         int i, ncolours;
1392         float *colours;
1393
1394         colours = midend_colours(fe->me, &ncolours);
1395
1396         fe->colours = snewn(ncolours, COLORREF);
1397         fe->brushes = snewn(ncolours, HBRUSH);
1398         fe->pens = snewn(ncolours, HPEN);
1399
1400         for (i = 0; i < ncolours; i++) {
1401             fe->colours[i] = RGB(255 * colours[i*3+0],
1402                                  255 * colours[i*3+1],
1403                                  255 * colours[i*3+2]);
1404             fe->brushes[i] = CreateSolidBrush(fe->colours[i]);
1405             fe->pens[i] = CreatePen(PS_SOLID, 1, fe->colours[i]);
1406         }
1407         sfree(colours);
1408     }
1409
1410     if (midend_wants_statusbar(fe->me)) {
1411         fe->statusbar = CreateWindowEx(0, STATUSCLASSNAME, TEXT("ooh"),
1412                                        WS_CHILD | WS_VISIBLE,
1413                                        0, 0, 0, 0, /* status bar does these */
1414                                        NULL, NULL, inst, NULL);
1415     } else
1416         fe->statusbar = NULL;
1417
1418     get_max_puzzle_size(fe, &x, &y);
1419     midend_size(fe->me, &x, &y, FALSE);
1420
1421     r.left = r.top = 0;
1422     r.right = x;
1423     r.bottom = y;
1424     AdjustWindowRectEx(&r, WINFLAGS, TRUE, 0);
1425
1426 #ifdef _WIN32_WCE
1427     fe->hwnd = CreateWindowEx(0, wGameName, wGameName,
1428                               WS_VISIBLE,
1429                               CW_USEDEFAULT, CW_USEDEFAULT,
1430                               CW_USEDEFAULT, CW_USEDEFAULT,
1431                               NULL, NULL, inst, NULL);
1432
1433     {
1434         SHMENUBARINFO mbi;
1435         RECT rc, rcBar, rcTB, rcClient;
1436
1437         memset (&mbi, 0, sizeof(SHMENUBARINFO));
1438         mbi.cbSize     = sizeof(SHMENUBARINFO);
1439         mbi.hwndParent = fe->hwnd;
1440         mbi.nToolBarId = IDR_MENUBAR1;
1441         mbi.hInstRes   = inst;
1442
1443         SHCreateMenuBar(&mbi);
1444
1445         GetWindowRect(fe->hwnd, &rc);
1446         GetWindowRect(mbi.hwndMB, &rcBar);
1447         rc.bottom -= rcBar.bottom - rcBar.top;
1448         MoveWindow(fe->hwnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, FALSE);
1449
1450         if (thegame.flags & REQUIRE_NUMPAD)
1451         {
1452             fe->numpad = CreateToolbarEx (fe->hwnd,
1453                                           WS_VISIBLE | WS_CHILD | CCS_NOPARENTALIGN | TBSTYLE_FLAT,
1454                                           0, 10, inst, IDR_PADTOOLBAR,
1455                                           tbNumpadButtons, sizeof (tbNumpadButtons) / sizeof (TBBUTTON),
1456                                           0, 0, 14, 15, sizeof (TBBUTTON));
1457             GetWindowRect(fe->numpad, &rcTB);
1458             GetClientRect(fe->hwnd, &rcClient);
1459             MoveWindow(fe->numpad, 
1460                        0, 
1461                        rcClient.bottom - (rcTB.bottom - rcTB.top) - 1,
1462                        rcClient.right,
1463                        rcTB.bottom - rcTB.top,
1464                        FALSE);
1465             SendMessage(fe->numpad, TB_SETINDENT, (rcClient.right - (10 * 21)) / 2, 0);
1466         }
1467         else
1468             fe->numpad = NULL;
1469     }
1470 #else
1471     fe->hwnd = CreateWindowEx(0, thegame.name, thegame.name,
1472                               WS_OVERLAPPEDWINDOW &~
1473                               (WS_THICKFRAME | WS_MAXIMIZEBOX),
1474                               CW_USEDEFAULT, CW_USEDEFAULT,
1475                               r.right - r.left, r.bottom - r.top,
1476                               NULL, NULL, inst, NULL);
1477 #endif
1478
1479     if (midend_wants_statusbar(fe->me)) {
1480         RECT sr;
1481         DestroyWindow(fe->statusbar);
1482         fe->statusbar = CreateWindowEx(0, STATUSCLASSNAME, TEXT("ooh"),
1483                                        WS_CHILD | WS_VISIBLE,
1484                                        0, 0, 0, 0, /* status bar does these */
1485                                        fe->hwnd, NULL, inst, NULL);
1486 #ifdef _WIN32_WCE
1487         /* Flat status bar looks better on the Pocket PC */
1488         SendMessage(fe->statusbar, SB_SIMPLE, (WPARAM) TRUE, 0);
1489         SendMessage(fe->statusbar, SB_SETTEXT,
1490                                 (WPARAM) 255 | SBT_NOBORDERS,
1491                                 (LPARAM) L"");
1492 #endif
1493
1494         /*
1495          * Now resize the window to take account of the status bar.
1496          */
1497         GetWindowRect(fe->statusbar, &sr);
1498         GetWindowRect(fe->hwnd, &r);
1499 #ifndef _WIN32_WCE
1500         SetWindowPos(fe->hwnd, NULL, 0, 0, r.right - r.left,
1501                      r.bottom - r.top + sr.bottom - sr.top,
1502                      SWP_NOMOVE | SWP_NOZORDER);
1503 #endif
1504     } else {
1505         fe->statusbar = NULL;
1506     }
1507
1508     {
1509 #ifndef _WIN32_WCE
1510         HMENU bar = CreateMenu();
1511         HMENU menu = CreateMenu();
1512
1513         AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)menu, "Game");
1514 #else
1515         HMENU menu = SHGetSubMenu(SHFindMenuBar(fe->hwnd), ID_GAME);
1516         DeleteMenu(menu, 0, MF_BYPOSITION);
1517 #endif
1518         AppendMenu(menu, MF_ENABLED, IDM_NEW, TEXT("New"));
1519         AppendMenu(menu, MF_ENABLED, IDM_RESTART, TEXT("Restart"));
1520 #ifndef _WIN32_WCE
1521         AppendMenu(menu, MF_ENABLED, IDM_DESC, TEXT("Specific..."));
1522         AppendMenu(menu, MF_ENABLED, IDM_SEED, TEXT("Random Seed..."));
1523 #endif
1524
1525         if ((fe->npresets = midend_num_presets(fe->me)) > 0 ||
1526             thegame.can_configure) {
1527             int i;
1528 #ifndef _WIN32_WCE
1529             HMENU sub = CreateMenu();
1530
1531             AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)sub, "Type");
1532 #else
1533             HMENU sub = SHGetSubMenu(SHFindMenuBar(fe->hwnd), ID_TYPE);
1534             DeleteMenu(sub, 0, MF_BYPOSITION);
1535 #endif
1536             fe->presets = snewn(fe->npresets, game_params *);
1537
1538             for (i = 0; i < fe->npresets; i++) {
1539                 char *name;
1540 #ifdef _WIN32_WCE
1541                 TCHAR wName[255];
1542 #endif
1543
1544                 midend_fetch_preset(fe->me, i, &name, &fe->presets[i]);
1545
1546                 /*
1547                  * FIXME: we ought to go through and do something
1548                  * with ampersands here.
1549                  */
1550
1551 #ifndef _WIN32_WCE
1552                 AppendMenu(sub, MF_ENABLED, IDM_PRESETS + 0x10 * i, name);
1553 #else
1554                 MultiByteToWideChar (CP_ACP, 0, name, -1, wName, 255);
1555                 AppendMenu(sub, MF_ENABLED, IDM_PRESETS + 0x10 * i, wName);
1556 #endif
1557             }
1558             if (thegame.can_configure) {
1559                 AppendMenu(sub, MF_ENABLED, IDM_CONFIG, TEXT("Custom..."));
1560             }
1561         }
1562
1563         AppendMenu(menu, MF_SEPARATOR, 0, 0);
1564 #ifndef _WIN32_WCE
1565         AppendMenu(menu, MF_ENABLED, IDM_LOAD, TEXT("Load..."));
1566         AppendMenu(menu, MF_ENABLED, IDM_SAVE, TEXT("Save..."));
1567         AppendMenu(menu, MF_SEPARATOR, 0, 0);
1568         if (thegame.can_print) {
1569             AppendMenu(menu, MF_ENABLED, IDM_PRINT, TEXT("Print..."));
1570             AppendMenu(menu, MF_SEPARATOR, 0, 0);
1571         }
1572 #endif
1573         AppendMenu(menu, MF_ENABLED, IDM_UNDO, TEXT("Undo"));
1574         AppendMenu(menu, MF_ENABLED, IDM_REDO, TEXT("Redo"));
1575 #ifndef _WIN32_WCE
1576         if (thegame.can_format_as_text) {
1577             AppendMenu(menu, MF_SEPARATOR, 0, 0);
1578             AppendMenu(menu, MF_ENABLED, IDM_COPY, TEXT("Copy"));
1579         }
1580 #endif
1581         if (thegame.can_solve) {
1582             AppendMenu(menu, MF_SEPARATOR, 0, 0);
1583             AppendMenu(menu, MF_ENABLED, IDM_SOLVE, TEXT("Solve"));
1584         }
1585         AppendMenu(menu, MF_SEPARATOR, 0, 0);
1586 #ifndef _WIN32_WCE
1587         AppendMenu(menu, MF_ENABLED, IDM_QUIT, TEXT("Exit"));
1588         menu = CreateMenu();
1589         AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)menu, TEXT("Help"));
1590 #endif
1591         AppendMenu(menu, MF_ENABLED, IDM_ABOUT, TEXT("About"));
1592 #ifndef _WIN32_WCE
1593         if (help_type != NONE) {
1594             AppendMenu(menu, MF_SEPARATOR, 0, 0);
1595             AppendMenu(menu, MF_ENABLED, IDM_HELPC, TEXT("Contents"));
1596             if (help_topic) {
1597                 char *item;
1598                 assert(thegame.name);
1599                 item = snewn(9+strlen(thegame.name), char); /*ick*/
1600                 sprintf(item, "Help on %s", thegame.name);
1601                 AppendMenu(menu, MF_ENABLED, IDM_GAMEHELP, item);
1602                 sfree(item);
1603             }
1604         }
1605         SetMenu(fe->hwnd, bar);
1606 #endif
1607     }
1608
1609     fe->bitmap = NULL;
1610     new_game_size(fe); /* initialises fe->bitmap */
1611     check_window_size(fe, &x, &y);
1612
1613     SetWindowLong(fe->hwnd, GWL_USERDATA, (LONG)fe);
1614
1615     ShowWindow(fe->hwnd, SW_SHOWNORMAL);
1616     SetForegroundWindow(fe->hwnd);
1617
1618     midend_redraw(fe->me);
1619
1620     return fe;
1621 }
1622
1623 #ifdef _WIN32_WCE
1624 static HFONT dialog_title_font()
1625 {
1626     static HFONT hf = NULL;
1627     LOGFONT lf;
1628
1629     if (hf)
1630         return hf;
1631
1632     memset (&lf, 0, sizeof(LOGFONT));
1633     lf.lfHeight = -11; /* - ((8 * GetDeviceCaps(hdc, LOGPIXELSY)) / 72) */
1634     lf.lfWeight = FW_BOLD;
1635     wcscpy(lf.lfFaceName, TEXT("Tahoma"));
1636
1637     return hf = CreateFontIndirect(&lf);
1638 }
1639
1640 static void make_dialog_full_screen(HWND hwnd)
1641 {
1642     SHINITDLGINFO shidi;
1643
1644     /* Make dialog full screen */
1645     shidi.dwMask = SHIDIM_FLAGS;
1646     shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIZEDLGFULLSCREEN |
1647                     SHIDIF_EMPTYMENU;
1648     shidi.hDlg = hwnd;
1649     SHInitDialog(&shidi);
1650 }
1651 #endif
1652
1653 static int CALLBACK AboutDlgProc(HWND hwnd, UINT msg,
1654                                  WPARAM wParam, LPARAM lParam)
1655 {
1656     frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
1657
1658     switch (msg) {
1659       case WM_INITDIALOG:
1660 #ifdef _WIN32_WCE
1661         {
1662             char title[256];
1663
1664             make_dialog_full_screen(hwnd);
1665
1666             sprintf(title, "About %.250s", thegame.name);
1667             SetDlgItemTextA(hwnd, IDC_ABOUT_CAPTION, title);
1668
1669             SendDlgItemMessage(hwnd, IDC_ABOUT_CAPTION, WM_SETFONT,
1670                                (WPARAM) dialog_title_font(), 0);
1671
1672             SetDlgItemTextA(hwnd, IDC_ABOUT_GAME, thegame.name);
1673             SetDlgItemTextA(hwnd, IDC_ABOUT_VERSION, ver);
1674         }
1675 #endif
1676         return TRUE;
1677
1678       case WM_COMMAND:
1679         if (LOWORD(wParam) == IDOK)
1680 #ifdef _WIN32_WCE
1681             EndDialog(hwnd, 1);
1682 #else
1683             fe->dlg_done = 1;
1684 #endif
1685         return 0;
1686
1687       case WM_CLOSE:
1688 #ifdef _WIN32_WCE
1689         EndDialog(hwnd, 1);
1690 #else
1691         fe->dlg_done = 1;
1692 #endif
1693         return 0;
1694     }
1695
1696     return 0;
1697 }
1698
1699 /*
1700  * Wrappers on midend_{get,set}_config, which extend the CFG_*
1701  * enumeration to add CFG_PRINT.
1702  */
1703 static config_item *frontend_get_config(frontend *fe, int which,
1704                                         char **wintitle)
1705 {
1706     if (which < CFG_FRONTEND_SPECIFIC) {
1707         return midend_get_config(fe->me, which, wintitle);
1708     } else if (which == CFG_PRINT) {
1709         config_item *ret;
1710         int i;
1711
1712         *wintitle = snewn(40 + strlen(thegame.name), char);
1713         sprintf(*wintitle, "%s print setup", thegame.name);
1714
1715         ret = snewn(8, config_item);
1716
1717         i = 0;
1718
1719         ret[i].name = "Number of puzzles to print";
1720         ret[i].type = C_STRING;
1721         ret[i].sval = dupstr("1");
1722         ret[i].ival = 0;
1723         i++;
1724
1725         ret[i].name = "Number of puzzles across the page";
1726         ret[i].type = C_STRING;
1727         ret[i].sval = dupstr("1");
1728         ret[i].ival = 0;
1729         i++;
1730
1731         ret[i].name = "Number of puzzles down the page";
1732         ret[i].type = C_STRING;
1733         ret[i].sval = dupstr("1");
1734         ret[i].ival = 0;
1735         i++;
1736
1737         ret[i].name = "Percentage of standard size";
1738         ret[i].type = C_STRING;
1739         ret[i].sval = dupstr("100.0");
1740         ret[i].ival = 0;
1741         i++;
1742
1743         ret[i].name = "Include currently shown puzzle";
1744         ret[i].type = C_BOOLEAN;
1745         ret[i].sval = NULL;
1746         ret[i].ival = TRUE;
1747         i++;
1748
1749         ret[i].name = "Print solutions";
1750         ret[i].type = C_BOOLEAN;
1751         ret[i].sval = NULL;
1752         ret[i].ival = FALSE;
1753         i++;
1754
1755         if (thegame.can_print_in_colour) {
1756             ret[i].name = "Print in colour";
1757             ret[i].type = C_BOOLEAN;
1758             ret[i].sval = NULL;
1759             ret[i].ival = FALSE;
1760             i++;
1761         }
1762
1763         ret[i].name = NULL;
1764         ret[i].type = C_END;
1765         ret[i].sval = NULL;
1766         ret[i].ival = 0;
1767         i++;
1768
1769         return ret;
1770     } else {
1771         assert(!"We should never get here");
1772         return NULL;
1773     }
1774 }
1775
1776 static char *frontend_set_config(frontend *fe, int which, config_item *cfg)
1777 {
1778     if (which < CFG_FRONTEND_SPECIFIC) {
1779         return midend_set_config(fe->me, which, cfg);
1780     } else if (which == CFG_PRINT) {
1781         if ((fe->printcount = atoi(cfg[0].sval)) <= 0)
1782             return "Number of puzzles to print should be at least one";
1783         if ((fe->printw = atoi(cfg[1].sval)) <= 0)
1784             return "Number of puzzles across the page should be at least one";
1785         if ((fe->printh = atoi(cfg[2].sval)) <= 0)
1786             return "Number of puzzles down the page should be at least one";
1787         if ((fe->printscale = (float)atof(cfg[3].sval)) <= 0)
1788             return "Print size should be positive";
1789         fe->printcurr = cfg[4].ival;
1790         fe->printsolns = cfg[5].ival;
1791         fe->printcolour = thegame.can_print_in_colour && cfg[6].ival;
1792         return NULL;
1793     } else {
1794         assert(!"We should never get here");
1795         return "Internal error";
1796     }
1797 }
1798
1799 #ifdef _WIN32_WCE
1800 /* Separate version of mkctrl function for the Pocket PC. */
1801 /* Control coordinates should be specified in dialog units. */
1802 HWND mkctrl(frontend *fe, int x1, int x2, int y1, int y2,
1803             LPCTSTR wclass, int wstyle,
1804             int exstyle, const char *wtext, int wid)
1805 {
1806     RECT rc;
1807     TCHAR wwtext[256];
1808
1809     /* Convert dialog units into pixels */
1810     rc.left = x1;  rc.right  = x2;
1811     rc.top  = y1;  rc.bottom = y2;
1812     MapDialogRect(fe->cfgbox, &rc);
1813
1814     MultiByteToWideChar (CP_ACP, 0, wtext, -1, wwtext, 256);
1815
1816     return CreateWindowEx(exstyle, wclass, wwtext,
1817                           wstyle | WS_CHILD | WS_VISIBLE,
1818                           rc.left, rc.top,
1819                           rc.right - rc.left, rc.bottom - rc.top,
1820                           fe->cfgbox, (HMENU) wid, fe->inst, NULL);
1821 }
1822
1823 static void create_config_controls(frontend * fe)
1824 {
1825     int id, nctrls;
1826     int col1l, col1r, col2l, col2r, y;
1827     config_item *i;
1828     struct cfg_aux *j;
1829     HWND ctl;
1830
1831     /* Control placement done in dialog units */
1832     col1l = 4;   col1r = 96;   /* Label column */
1833     col2l = 100; col2r = 154;  /* Input column (edit boxes and combo boxes) */
1834
1835     /*
1836      * Count the controls so we can allocate cfgaux.
1837      */
1838     for (nctrls = 0, i = fe->cfg; i->type != C_END; i++)
1839         nctrls++;
1840     fe->cfgaux = snewn(nctrls, struct cfg_aux);
1841
1842     id = 1000;
1843     y = 22; /* Leave some room for the dialog title */
1844     for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++) {
1845         switch (i->type) {
1846           case C_STRING:
1847             /*
1848              * Edit box with a label beside it.
1849              */
1850             mkctrl(fe, col1l, col1r, y + 1, y + 11,
1851                    TEXT("Static"), SS_LEFTNOWORDWRAP, 0, i->name, id++);
1852             mkctrl(fe, col2l, col2r, y, y + 12,
1853                    TEXT("EDIT"), WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL,
1854                    0, "", (j->ctlid = id++));
1855             SetDlgItemTextA(fe->cfgbox, j->ctlid, i->sval);
1856             break;
1857
1858           case C_BOOLEAN:
1859             /*
1860              * Simple checkbox.
1861              */
1862             mkctrl(fe, col1l, col2r, y + 1, y + 11, TEXT("BUTTON"),
1863                    BS_NOTIFY | BS_AUTOCHECKBOX | WS_TABSTOP,
1864                    0, i->name, (j->ctlid = id++));
1865             CheckDlgButton(fe->cfgbox, j->ctlid, (i->ival != 0));
1866             break;
1867
1868           case C_CHOICES:
1869             /*
1870              * Drop-down list with a label beside it.
1871              */
1872             mkctrl(fe, col1l, col1r, y + 1, y + 11,
1873                    TEXT("STATIC"), SS_LEFTNOWORDWRAP, 0, i->name, id++);
1874             ctl = mkctrl(fe, col2l, col2r, y, y + 48,
1875                          TEXT("COMBOBOX"), WS_BORDER | WS_TABSTOP |
1876                          CBS_DROPDOWNLIST | CBS_HASSTRINGS,
1877                          0, "", (j->ctlid = id++));
1878             {
1879                 char c, *p, *q, *str;
1880
1881                 p = i->sval;
1882                 c = *p++;
1883                 while (*p) {
1884                     q = p;
1885                     while (*q && *q != c) q++;
1886                     str = snewn(q-p+1, char);
1887                     strncpy(str, p, q-p);
1888                     str[q-p] = '\0';
1889                     {
1890                         TCHAR ws[50];
1891                         MultiByteToWideChar (CP_ACP, 0, str, -1, ws, 50);
1892                         SendMessage(ctl, CB_ADDSTRING, 0, (LPARAM)ws);
1893                     }
1894                     
1895                     sfree(str);
1896                     if (*q) q++;
1897                     p = q;
1898                 }
1899             }
1900             SendMessage(ctl, CB_SETCURSEL, i->ival, 0);
1901             break;
1902         }
1903
1904         y += 15;
1905     }
1906
1907 }
1908 #endif
1909
1910 static int CALLBACK ConfigDlgProc(HWND hwnd, UINT msg,
1911                                   WPARAM wParam, LPARAM lParam)
1912 {
1913     frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
1914     config_item *i;
1915     struct cfg_aux *j;
1916
1917     switch (msg) {
1918       case WM_INITDIALOG:
1919 #ifdef _WIN32_WCE
1920         {
1921             char *title;
1922
1923             fe = (frontend *) lParam;
1924             SetWindowLong(hwnd, GWL_USERDATA, lParam);
1925             fe->cfgbox = hwnd;
1926
1927             fe->cfg = frontend_get_config(fe, fe->cfg_which, &title);
1928
1929             make_dialog_full_screen(hwnd);
1930
1931             SetDlgItemTextA(hwnd, IDC_CONFIG_CAPTION, title);
1932             SendDlgItemMessage(hwnd, IDC_CONFIG_CAPTION, WM_SETFONT,
1933                                (WPARAM) dialog_title_font(), 0);
1934
1935             create_config_controls(fe);
1936         }
1937 #endif
1938         return TRUE;
1939
1940       case WM_COMMAND:
1941         /*
1942          * OK and Cancel are special cases.
1943          */
1944         if ((LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)) {
1945             if (LOWORD(wParam) == IDOK) {
1946                 char *err = frontend_set_config(fe, fe->cfg_which, fe->cfg);
1947
1948                 if (err) {
1949                     MessageBox(hwnd, err, "Validation error",
1950                                MB_ICONERROR | MB_OK);
1951                 } else {
1952 #ifdef _WIN32_WCE
1953                     EndDialog(hwnd, 2);
1954 #else
1955                     fe->dlg_done = 2;
1956 #endif
1957                 }
1958             } else {
1959 #ifdef _WIN32_WCE
1960                 EndDialog(hwnd, 1);
1961 #else
1962                 fe->dlg_done = 1;
1963 #endif
1964             }
1965             return 0;
1966         }
1967
1968         /*
1969          * First find the control whose id this is.
1970          */
1971         for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++) {
1972             if (j->ctlid == LOWORD(wParam))
1973                 break;
1974         }
1975         if (i->type == C_END)
1976             return 0;                  /* not our problem */
1977
1978         if (i->type == C_STRING && HIWORD(wParam) == EN_CHANGE) {
1979             char buffer[4096];
1980 #ifdef _WIN32_WCE
1981             TCHAR wBuffer[4096];
1982             GetDlgItemText(fe->cfgbox, j->ctlid, wBuffer, 4096);
1983             WideCharToMultiByte(CP_ACP, 0, wBuffer, -1, buffer, 4096, NULL, NULL);
1984 #else
1985             GetDlgItemText(fe->cfgbox, j->ctlid, buffer, lenof(buffer));
1986 #endif
1987             buffer[lenof(buffer)-1] = '\0';
1988             sfree(i->sval);
1989             i->sval = dupstr(buffer);
1990         } else if (i->type == C_BOOLEAN && 
1991                    (HIWORD(wParam) == BN_CLICKED ||
1992                     HIWORD(wParam) == BN_DBLCLK)) {
1993             i->ival = IsDlgButtonChecked(fe->cfgbox, j->ctlid);
1994         } else if (i->type == C_CHOICES &&
1995                    HIWORD(wParam) == CBN_SELCHANGE) {
1996             i->ival = SendDlgItemMessage(fe->cfgbox, j->ctlid,
1997                                          CB_GETCURSEL, 0, 0);
1998         }
1999
2000         return 0;
2001
2002       case WM_CLOSE:
2003         fe->dlg_done = 1;
2004         return 0;
2005     }
2006
2007     return 0;
2008 }
2009
2010 #ifndef _WIN32_WCE
2011 HWND mkctrl(frontend *fe, int x1, int x2, int y1, int y2,
2012             char *wclass, int wstyle,
2013             int exstyle, const char *wtext, int wid)
2014 {
2015     HWND ret;
2016     ret = CreateWindowEx(exstyle, wclass, wtext,
2017                          wstyle | WS_CHILD | WS_VISIBLE, x1, y1, x2-x1, y2-y1,
2018                          fe->cfgbox, (HMENU) wid, fe->inst, NULL);
2019     SendMessage(ret, WM_SETFONT, (WPARAM)fe->cfgfont, MAKELPARAM(TRUE, 0));
2020     return ret;
2021 }
2022 #endif
2023
2024 static void about(frontend *fe)
2025 {
2026 #ifdef _WIN32_WCE
2027     DialogBox(fe->inst, MAKEINTRESOURCE(IDD_ABOUT), fe->hwnd, AboutDlgProc);
2028 #else
2029     int i;
2030     WNDCLASS wc;
2031     MSG msg;
2032     TEXTMETRIC tm;
2033     HDC hdc;
2034     HFONT oldfont;
2035     SIZE size;
2036     int gm, id;
2037     int winwidth, winheight, y;
2038     int height, width, maxwid;
2039     const char *strings[16];
2040     int lengths[16];
2041     int nstrings = 0;
2042     char titlebuf[512];
2043
2044     sprintf(titlebuf, "About %.250s", thegame.name);
2045
2046     strings[nstrings++] = thegame.name;
2047     strings[nstrings++] = "from Simon Tatham's Portable Puzzle Collection";
2048     strings[nstrings++] = ver;
2049
2050     wc.style = CS_DBLCLKS | CS_SAVEBITS;
2051     wc.lpfnWndProc = DefDlgProc;
2052     wc.cbClsExtra = 0;
2053     wc.cbWndExtra = DLGWINDOWEXTRA + 8;
2054     wc.hInstance = fe->inst;
2055     wc.hIcon = NULL;
2056     wc.hCursor = LoadCursor(NULL, IDC_ARROW);
2057     wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
2058     wc.lpszMenuName = NULL;
2059     wc.lpszClassName = "GameAboutBox";
2060     RegisterClass(&wc);
2061
2062     hdc = GetDC(fe->hwnd);
2063     SetMapMode(hdc, MM_TEXT);
2064
2065     fe->dlg_done = FALSE;
2066
2067     fe->cfgfont = CreateFont(-MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72),
2068                              0, 0, 0, 0,
2069                              FALSE, FALSE, FALSE, DEFAULT_CHARSET,
2070                              OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
2071                              DEFAULT_QUALITY,
2072                              FF_SWISS,
2073                              "MS Shell Dlg");
2074
2075     oldfont = SelectObject(hdc, fe->cfgfont);
2076     if (GetTextMetrics(hdc, &tm)) {
2077         height = tm.tmAscent + tm.tmDescent;
2078         width = tm.tmAveCharWidth;
2079     } else {
2080         height = width = 30;
2081     }
2082
2083     /*
2084      * Figure out the layout of the About box by measuring the
2085      * length of each piece of text.
2086      */
2087     maxwid = 0;
2088     winheight = height/2;
2089
2090     for (i = 0; i < nstrings; i++) {
2091         if (GetTextExtentPoint32(hdc, strings[i], strlen(strings[i]), &size))
2092             lengths[i] = size.cx;
2093         else
2094             lengths[i] = 0;            /* *shrug* */
2095         if (maxwid < lengths[i])
2096             maxwid = lengths[i];
2097         winheight += height * 3 / 2 + (height / 2);
2098     }
2099
2100     winheight += height + height * 7 / 4;      /* OK button */
2101     winwidth = maxwid + 4*width;
2102
2103     SelectObject(hdc, oldfont);
2104     ReleaseDC(fe->hwnd, hdc);
2105
2106     /*
2107      * Create the dialog, now that we know its size.
2108      */
2109     {
2110         RECT r, r2;
2111
2112         r.left = r.top = 0;
2113         r.right = winwidth;
2114         r.bottom = winheight;
2115
2116         AdjustWindowRectEx(&r, (WS_OVERLAPPEDWINDOW /*|
2117                                 DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
2118                                 WS_CAPTION | WS_SYSMENU*/) &~
2119                            (WS_MAXIMIZEBOX | WS_OVERLAPPED),
2120                            FALSE, 0);
2121
2122         /*
2123          * Centre the dialog on its parent window.
2124          */
2125         r.right -= r.left;
2126         r.bottom -= r.top;
2127         GetWindowRect(fe->hwnd, &r2);
2128         r.left = (r2.left + r2.right - r.right) / 2;
2129         r.top = (r2.top + r2.bottom - r.bottom) / 2;
2130         r.right += r.left;
2131         r.bottom += r.top;
2132
2133         fe->cfgbox = CreateWindowEx(0, wc.lpszClassName, titlebuf,
2134                                     DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
2135                                     WS_CAPTION | WS_SYSMENU,
2136                                     r.left, r.top,
2137                                     r.right-r.left, r.bottom-r.top,
2138                                     fe->hwnd, NULL, fe->inst, NULL);
2139     }
2140
2141     SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE);
2142
2143     SetWindowLong(fe->cfgbox, GWL_USERDATA, (LONG)fe);
2144     SetWindowLong(fe->cfgbox, DWL_DLGPROC, (LONG)AboutDlgProc);
2145
2146     id = 1000;
2147     y = height/2;
2148     for (i = 0; i < nstrings; i++) {
2149         int border = width*2 + (maxwid - lengths[i]) / 2;
2150         mkctrl(fe, border, border+lengths[i], y+height*1/8, y+height*9/8,
2151                "Static", 0, 0, strings[i], id++);
2152         y += height*3/2;
2153
2154         assert(y < winheight);
2155         y += height/2;
2156     }
2157
2158     y += height/2;                     /* extra space before OK */
2159     mkctrl(fe, width*2, maxwid+width*2, y, y+height*7/4, "BUTTON",
2160            BS_PUSHBUTTON | BS_NOTIFY | WS_TABSTOP | BS_DEFPUSHBUTTON, 0,
2161            "OK", IDOK);
2162
2163     SendMessage(fe->cfgbox, WM_INITDIALOG, 0, 0);
2164
2165     EnableWindow(fe->hwnd, FALSE);
2166     ShowWindow(fe->cfgbox, SW_SHOWNORMAL);
2167     while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
2168         if (!IsDialogMessage(fe->cfgbox, &msg))
2169             DispatchMessage(&msg);
2170         if (fe->dlg_done)
2171             break;
2172     }
2173     EnableWindow(fe->hwnd, TRUE);
2174     SetForegroundWindow(fe->hwnd);
2175     DestroyWindow(fe->cfgbox);
2176     DeleteObject(fe->cfgfont);
2177 #endif
2178 }
2179
2180 static int get_config(frontend *fe, int which)
2181 {
2182 #ifdef _WIN32_WCE
2183     fe->cfg_which = which;
2184
2185     return DialogBoxParam(fe->inst,
2186                           MAKEINTRESOURCE(IDD_CONFIG),
2187                           fe->hwnd, ConfigDlgProc,
2188                           (LPARAM) fe) == 2;
2189 #else
2190     config_item *i;
2191     struct cfg_aux *j;
2192     char *title;
2193     WNDCLASS wc;
2194     MSG msg;
2195     TEXTMETRIC tm;
2196     HDC hdc;
2197     HFONT oldfont;
2198     SIZE size;
2199     HWND ctl;
2200     int gm, id, nctrls;
2201     int winwidth, winheight, col1l, col1r, col2l, col2r, y;
2202     int height, width, maxlabel, maxcheckbox;
2203
2204     wc.style = CS_DBLCLKS | CS_SAVEBITS;
2205     wc.lpfnWndProc = DefDlgProc;
2206     wc.cbClsExtra = 0;
2207     wc.cbWndExtra = DLGWINDOWEXTRA + 8;
2208     wc.hInstance = fe->inst;
2209     wc.hIcon = NULL;
2210     wc.hCursor = LoadCursor(NULL, IDC_ARROW);
2211     wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
2212     wc.lpszMenuName = NULL;
2213     wc.lpszClassName = "GameConfigBox";
2214     RegisterClass(&wc);
2215
2216     hdc = GetDC(fe->hwnd);
2217     SetMapMode(hdc, MM_TEXT);
2218
2219     fe->dlg_done = FALSE;
2220
2221     fe->cfgfont = CreateFont(-MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72),
2222                              0, 0, 0, 0,
2223                              FALSE, FALSE, FALSE, DEFAULT_CHARSET,
2224                              OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
2225                              DEFAULT_QUALITY,
2226                              FF_SWISS,
2227                              "MS Shell Dlg");
2228
2229     oldfont = SelectObject(hdc, fe->cfgfont);
2230     if (GetTextMetrics(hdc, &tm)) {
2231         height = tm.tmAscent + tm.tmDescent;
2232         width = tm.tmAveCharWidth;
2233     } else {
2234         height = width = 30;
2235     }
2236
2237     fe->cfg = frontend_get_config(fe, which, &title);
2238     fe->cfg_which = which;
2239
2240     /*
2241      * Figure out the layout of the config box by measuring the
2242      * length of each piece of text.
2243      */
2244     maxlabel = maxcheckbox = 0;
2245     winheight = height/2;
2246
2247     for (i = fe->cfg; i->type != C_END; i++) {
2248         switch (i->type) {
2249           case C_STRING:
2250           case C_CHOICES:
2251             /*
2252              * Both these control types have a label filling only
2253              * the left-hand column of the box.
2254              */
2255             if (GetTextExtentPoint32(hdc, i->name, strlen(i->name), &size) &&
2256                 maxlabel < size.cx)
2257                 maxlabel = size.cx;
2258             winheight += height * 3 / 2 + (height / 2);
2259             break;
2260
2261           case C_BOOLEAN:
2262             /*
2263              * Checkboxes take up the whole of the box width.
2264              */
2265             if (GetTextExtentPoint32(hdc, i->name, strlen(i->name), &size) &&
2266                 maxcheckbox < size.cx)
2267                 maxcheckbox = size.cx;
2268             winheight += height + (height / 2);
2269             break;
2270         }
2271     }
2272
2273     winheight += height + height * 7 / 4;      /* OK / Cancel buttons */
2274
2275     col1l = 2*width;
2276     col1r = col1l + maxlabel;
2277     col2l = col1r + 2*width;
2278     col2r = col2l + 30*width;
2279     if (col2r < col1l+2*height+maxcheckbox)
2280         col2r = col1l+2*height+maxcheckbox;
2281     winwidth = col2r + 2*width;
2282
2283     SelectObject(hdc, oldfont);
2284     ReleaseDC(fe->hwnd, hdc);
2285
2286     /*
2287      * Create the dialog, now that we know its size.
2288      */
2289     {
2290         RECT r, r2;
2291
2292         r.left = r.top = 0;
2293         r.right = winwidth;
2294         r.bottom = winheight;
2295
2296         AdjustWindowRectEx(&r, (WS_OVERLAPPEDWINDOW /*|
2297                                 DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
2298                                 WS_CAPTION | WS_SYSMENU*/) &~
2299                            (WS_MAXIMIZEBOX | WS_OVERLAPPED),
2300                            FALSE, 0);
2301
2302         /*
2303          * Centre the dialog on its parent window.
2304          */
2305         r.right -= r.left;
2306         r.bottom -= r.top;
2307         GetWindowRect(fe->hwnd, &r2);
2308         r.left = (r2.left + r2.right - r.right) / 2;
2309         r.top = (r2.top + r2.bottom - r.bottom) / 2;
2310         r.right += r.left;
2311         r.bottom += r.top;
2312
2313         fe->cfgbox = CreateWindowEx(0, wc.lpszClassName, title,
2314                                     DS_MODALFRAME | WS_POPUP | WS_VISIBLE |
2315                                     WS_CAPTION | WS_SYSMENU,
2316                                     r.left, r.top,
2317                                     r.right-r.left, r.bottom-r.top,
2318                                     fe->hwnd, NULL, fe->inst, NULL);
2319         sfree(title);
2320     }
2321
2322     SendMessage(fe->cfgbox, WM_SETFONT, (WPARAM)fe->cfgfont, FALSE);
2323
2324     SetWindowLong(fe->cfgbox, GWL_USERDATA, (LONG)fe);
2325     SetWindowLong(fe->cfgbox, DWL_DLGPROC, (LONG)ConfigDlgProc);
2326
2327     /*
2328      * Count the controls so we can allocate cfgaux.
2329      */
2330     for (nctrls = 0, i = fe->cfg; i->type != C_END; i++)
2331         nctrls++;
2332     fe->cfgaux = snewn(nctrls, struct cfg_aux);
2333
2334     id = 1000;
2335     y = height/2;
2336     for (i = fe->cfg, j = fe->cfgaux; i->type != C_END; i++, j++) {
2337         switch (i->type) {
2338           case C_STRING:
2339             /*
2340              * Edit box with a label beside it.
2341              */
2342             mkctrl(fe, col1l, col1r, y+height*1/8, y+height*9/8,
2343                    "Static", 0, 0, i->name, id++);
2344             ctl = mkctrl(fe, col2l, col2r, y, y+height*3/2,
2345                          "EDIT", WS_TABSTOP | ES_AUTOHSCROLL,
2346                          WS_EX_CLIENTEDGE, "", (j->ctlid = id++));
2347             SetWindowText(ctl, i->sval);
2348             y += height*3/2;
2349             break;
2350
2351           case C_BOOLEAN:
2352             /*
2353              * Simple checkbox.
2354              */
2355             mkctrl(fe, col1l, col2r, y, y+height, "BUTTON",
2356                    BS_NOTIFY | BS_AUTOCHECKBOX | WS_TABSTOP,
2357                    0, i->name, (j->ctlid = id++));
2358             CheckDlgButton(fe->cfgbox, j->ctlid, (i->ival != 0));
2359             y += height;
2360             break;
2361
2362           case C_CHOICES:
2363             /*
2364              * Drop-down list with a label beside it.
2365              */
2366             mkctrl(fe, col1l, col1r, y+height*1/8, y+height*9/8,
2367                    "STATIC", 0, 0, i->name, id++);
2368             ctl = mkctrl(fe, col2l, col2r, y, y+height*41/2,
2369                          "COMBOBOX", WS_TABSTOP |
2370                          CBS_DROPDOWNLIST | CBS_HASSTRINGS,
2371                          WS_EX_CLIENTEDGE, "", (j->ctlid = id++));
2372             {
2373                 char c, *p, *q, *str;
2374
2375                 SendMessage(ctl, CB_RESETCONTENT, 0, 0);
2376                 p = i->sval;
2377                 c = *p++;
2378                 while (*p) {
2379                     q = p;
2380                     while (*q && *q != c) q++;
2381                     str = snewn(q-p+1, char);
2382                     strncpy(str, p, q-p);
2383                     str[q-p] = '\0';
2384                     SendMessage(ctl, CB_ADDSTRING, 0, (LPARAM)str);
2385                     sfree(str);
2386                     if (*q) q++;
2387                     p = q;
2388                 }
2389             }
2390
2391             SendMessage(ctl, CB_SETCURSEL, i->ival, 0);
2392
2393             y += height*3/2;
2394             break;
2395         }
2396
2397         assert(y < winheight);
2398         y += height/2;
2399     }
2400
2401     y += height/2;                     /* extra space before OK and Cancel */
2402     mkctrl(fe, col1l, (col1l+col2r)/2-width, y, y+height*7/4, "BUTTON",
2403            BS_PUSHBUTTON | BS_NOTIFY | WS_TABSTOP | BS_DEFPUSHBUTTON, 0,
2404            "OK", IDOK);
2405     mkctrl(fe, (col1l+col2r)/2+width, col2r, y, y+height*7/4, "BUTTON",
2406            BS_PUSHBUTTON | BS_NOTIFY | WS_TABSTOP, 0, "Cancel", IDCANCEL);
2407
2408     SendMessage(fe->cfgbox, WM_INITDIALOG, 0, 0);
2409
2410     EnableWindow(fe->hwnd, FALSE);
2411     ShowWindow(fe->cfgbox, SW_SHOWNORMAL);
2412     while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
2413         if (!IsDialogMessage(fe->cfgbox, &msg))
2414             DispatchMessage(&msg);
2415         if (fe->dlg_done)
2416             break;
2417     }
2418     EnableWindow(fe->hwnd, TRUE);
2419     SetForegroundWindow(fe->hwnd);
2420     DestroyWindow(fe->cfgbox);
2421     DeleteObject(fe->cfgfont);
2422
2423     free_cfg(fe->cfg);
2424     sfree(fe->cfgaux);
2425
2426     return (fe->dlg_done == 2);
2427 #endif
2428 }
2429
2430 #ifdef _WIN32_WCE
2431 static void calculate_bitmap_position(frontend *fe, int x, int y)
2432 {
2433     /* Pocket PC - center the game in the full screen window */
2434     int yMargin;
2435     RECT rcClient;
2436
2437     GetClientRect(fe->hwnd, &rcClient);
2438     fe->bitmapPosition.left = (rcClient.right  - x) / 2;
2439     yMargin = rcClient.bottom - y;
2440
2441     if (fe->numpad != NULL) {
2442         RECT rcPad;
2443         GetWindowRect(fe->numpad, &rcPad);
2444         yMargin -= rcPad.bottom - rcPad.top;
2445     }
2446
2447     if (fe->statusbar != NULL) {
2448         RECT rcStatus;
2449         GetWindowRect(fe->statusbar, &rcStatus);
2450         yMargin -= rcStatus.bottom - rcStatus.top;
2451     }
2452
2453     fe->bitmapPosition.top = yMargin / 2;
2454
2455     fe->bitmapPosition.right  = fe->bitmapPosition.left + x;
2456     fe->bitmapPosition.bottom = fe->bitmapPosition.top  + y;
2457 }
2458 #else
2459 static void calculate_bitmap_position(frontend *fe, int x, int y)
2460 {
2461     /* Plain Windows - position the game in the upper-left corner */
2462     fe->bitmapPosition.left = 0;
2463     fe->bitmapPosition.top = 0;
2464     fe->bitmapPosition.right  = fe->bitmapPosition.left + x;
2465     fe->bitmapPosition.bottom = fe->bitmapPosition.top  + y;
2466 }
2467 #endif
2468
2469 static void new_game_size(frontend *fe)
2470 {
2471     RECT r, sr;
2472     HDC hdc;
2473     int x, y;
2474
2475     get_max_puzzle_size(fe, &x, &y);
2476     midend_size(fe->me, &x, &y, FALSE);
2477
2478     r.left = r.top = 0;
2479     r.right = x;
2480     r.bottom = y;
2481     AdjustWindowRectEx(&r, WINFLAGS, TRUE, 0);
2482
2483     if (fe->statusbar != NULL) {
2484         GetWindowRect(fe->statusbar, &sr);
2485     } else {
2486         sr.left = sr.right = sr.top = sr.bottom = 0;
2487     }
2488 #ifndef _WIN32_WCE
2489     SetWindowPos(fe->hwnd, NULL, 0, 0,
2490                  r.right - r.left,
2491                  r.bottom - r.top + sr.bottom - sr.top,
2492                  SWP_NOMOVE | SWP_NOZORDER);
2493 #endif
2494
2495     check_window_size(fe, &x, &y);
2496
2497 #ifndef _WIN32_WCE
2498     if (fe->statusbar != NULL)
2499         SetWindowPos(fe->statusbar, NULL, 0, y, x,
2500                      sr.bottom - sr.top, SWP_NOZORDER);
2501 #endif
2502
2503     if (fe->bitmap) DeleteObject(fe->bitmap);
2504
2505     hdc = GetDC(fe->hwnd);
2506     fe->bitmap = CreateCompatibleBitmap(hdc, x, y);
2507     calculate_bitmap_position(fe, x, y);
2508     ReleaseDC(fe->hwnd, hdc);
2509
2510 #ifdef _WIN32_WCE
2511     InvalidateRect(fe->hwnd, NULL, TRUE);
2512 #endif
2513     midend_redraw(fe->me);
2514 }
2515
2516 static void new_game_type(frontend *fe)
2517 {
2518     midend_new_game(fe->me);
2519     new_game_size(fe);
2520 }
2521
2522 static int is_alt_pressed(void)
2523 {
2524     BYTE keystate[256];
2525     int r = GetKeyboardState(keystate);
2526     if (!r)
2527         return FALSE;
2528     if (keystate[VK_MENU] & 0x80)
2529         return TRUE;
2530     if (keystate[VK_RMENU] & 0x80)
2531         return TRUE;
2532     return FALSE;
2533 }
2534
2535 static void savefile_write(void *wctx, void *buf, int len)
2536 {
2537     FILE *fp = (FILE *)wctx;
2538     fwrite(buf, 1, len, fp);
2539 }
2540
2541 static int savefile_read(void *wctx, void *buf, int len)
2542 {
2543     FILE *fp = (FILE *)wctx;
2544     int ret;
2545
2546     ret = fread(buf, 1, len, fp);
2547     return (ret == len);
2548 }
2549
2550 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
2551                                 WPARAM wParam, LPARAM lParam)
2552 {
2553     frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
2554     int cmd;
2555
2556     switch (message) {
2557       case WM_CLOSE:
2558         DestroyWindow(hwnd);
2559         return 0;
2560       case WM_COMMAND:
2561 #ifdef _WIN32_WCE
2562         /* Numeric pad sends WM_COMMAND messages */
2563         if ((wParam >= IDM_KEYEMUL) && (wParam < IDM_KEYEMUL + 256))
2564         {
2565             midend_process_key(fe->me, 0, 0, wParam - IDM_KEYEMUL);
2566         }
2567 #endif
2568         cmd = wParam & ~0xF;           /* low 4 bits reserved to Windows */
2569         switch (cmd) {
2570           case IDM_NEW:
2571             if (!midend_process_key(fe->me, 0, 0, 'n'))
2572                 PostQuitMessage(0);
2573             break;
2574           case IDM_RESTART:
2575             midend_restart_game(fe->me);
2576             break;
2577           case IDM_UNDO:
2578             if (!midend_process_key(fe->me, 0, 0, 'u'))
2579                 PostQuitMessage(0);
2580             break;
2581           case IDM_REDO:
2582             if (!midend_process_key(fe->me, 0, 0, '\x12'))
2583                 PostQuitMessage(0);
2584             break;
2585           case IDM_COPY:
2586             {
2587                 char *text = midend_text_format(fe->me);
2588                 if (text)
2589                     write_clip(hwnd, text);
2590                 else
2591                     MessageBeep(MB_ICONWARNING);
2592                 sfree(text);
2593             }
2594             break;
2595           case IDM_SOLVE:
2596             {
2597                 char *msg = midend_solve(fe->me);
2598                 if (msg)
2599                     MessageBox(hwnd, msg, "Unable to solve",
2600                                MB_ICONERROR | MB_OK);
2601             }
2602             break;
2603           case IDM_QUIT:
2604             if (!midend_process_key(fe->me, 0, 0, 'q'))
2605                 PostQuitMessage(0);
2606             break;
2607           case IDM_CONFIG:
2608             if (get_config(fe, CFG_SETTINGS))
2609                 new_game_type(fe);
2610             break;
2611           case IDM_SEED:
2612             if (get_config(fe, CFG_SEED))
2613                 new_game_type(fe);
2614             break;
2615           case IDM_DESC:
2616             if (get_config(fe, CFG_DESC))
2617                 new_game_type(fe);
2618             break;
2619           case IDM_PRINT:
2620             if (get_config(fe, CFG_PRINT))
2621                 print(fe);
2622             break;
2623           case IDM_ABOUT:
2624             about(fe);
2625             break;
2626           case IDM_LOAD:
2627           case IDM_SAVE:
2628             {
2629                 OPENFILENAME of;
2630                 char filename[FILENAME_MAX];
2631                 int ret;
2632
2633                 memset(&of, 0, sizeof(of));
2634                 of.hwndOwner = hwnd;
2635                 of.lpstrFilter = "All Files (*.*)\0*\0\0\0";
2636                 of.lpstrCustomFilter = NULL;
2637                 of.nFilterIndex = 1;
2638                 of.lpstrFile = filename;
2639                 filename[0] = '\0';
2640                 of.nMaxFile = lenof(filename);
2641                 of.lpstrFileTitle = NULL;
2642                 of.lpstrTitle = (cmd == IDM_SAVE ?
2643                                  "Enter name of game file to save" :
2644                                  "Enter name of saved game file to load");
2645                 of.Flags = 0;
2646 #ifdef OPENFILENAME_SIZE_VERSION_400
2647                 of.lStructSize = OPENFILENAME_SIZE_VERSION_400;
2648 #else
2649                 of.lStructSize = sizeof(of);
2650 #endif
2651                 of.lpstrInitialDir = NULL;
2652
2653                 if (cmd == IDM_SAVE)
2654                     ret = GetSaveFileName(&of);
2655                 else
2656                     ret = GetOpenFileName(&of);
2657
2658                 if (ret) {
2659                     if (cmd == IDM_SAVE) {
2660                         FILE *fp;
2661
2662                         if ((fp = fopen(filename, "r")) != NULL) {
2663                             char buf[256 + FILENAME_MAX];
2664                             fclose(fp);
2665                             /* file exists */
2666
2667                             sprintf(buf, "Are you sure you want to overwrite"
2668                                     " the file \"%.*s\"?",
2669                                     FILENAME_MAX, filename);
2670                             if (MessageBox(hwnd, buf, "Question",
2671                                            MB_YESNO | MB_ICONQUESTION)
2672                                 != IDYES)
2673                                 break;
2674                         }
2675
2676                         fp = fopen(filename, "w");
2677
2678                         if (!fp) {
2679                             MessageBox(hwnd, "Unable to open save file",
2680                                        "Error", MB_ICONERROR | MB_OK);
2681                             break;
2682                         }
2683
2684                         midend_serialise(fe->me, savefile_write, fp);
2685
2686                         fclose(fp);
2687                     } else {
2688                         FILE *fp = fopen(filename, "r");
2689                         char *err;
2690
2691                         if (!fp) {
2692                             MessageBox(hwnd, "Unable to open saved game file",
2693                                        "Error", MB_ICONERROR | MB_OK);
2694                             break;
2695                         }
2696
2697                         err = midend_deserialise(fe->me, savefile_read, fp);
2698
2699                         fclose(fp);
2700
2701                         if (err) {
2702                             MessageBox(hwnd, err, "Error", MB_ICONERROR|MB_OK);
2703                             break;
2704                         }
2705
2706                         new_game_size(fe);
2707                     }
2708                 }
2709             }
2710
2711             break;
2712 #ifndef _WIN32_WCE
2713           case IDM_HELPC:
2714             start_help(fe, NULL);
2715             break;
2716           case IDM_GAMEHELP:
2717             start_help(fe, help_topic);
2718             break;
2719 #endif
2720           default:
2721             {
2722                 int p = ((wParam &~ 0xF) - IDM_PRESETS) / 0x10;
2723
2724                 if (p >= 0 && p < fe->npresets) {
2725                     midend_set_params(fe->me, fe->presets[p]);
2726                     new_game_type(fe);
2727                 }
2728             }
2729             break;
2730         }
2731         break;
2732       case WM_DESTROY:
2733 #ifndef _WIN32_WCE
2734         stop_help(fe);
2735 #endif
2736         PostQuitMessage(0);
2737         return 0;
2738       case WM_PAINT:
2739         {
2740             PAINTSTRUCT p;
2741             HDC hdc, hdc2;
2742             HBITMAP prevbm;
2743             RECT rcDest;
2744
2745             hdc = BeginPaint(hwnd, &p);
2746             hdc2 = CreateCompatibleDC(hdc);
2747             prevbm = SelectObject(hdc2, fe->bitmap);
2748 #ifdef _WIN32_WCE
2749             FillRect(hdc, &(p.rcPaint), (HBRUSH) GetStockObject(WHITE_BRUSH));
2750 #endif
2751             IntersectRect(&rcDest, &(fe->bitmapPosition), &(p.rcPaint));
2752             BitBlt(hdc,
2753                    rcDest.left, rcDest.top,
2754                    rcDest.right - rcDest.left,
2755                    rcDest.bottom - rcDest.top,
2756                    hdc2,
2757                    rcDest.left - fe->bitmapPosition.left,
2758                    rcDest.top - fe->bitmapPosition.top,
2759                    SRCCOPY);
2760             SelectObject(hdc2, prevbm);
2761             DeleteDC(hdc2);
2762             EndPaint(hwnd, &p);
2763         }
2764         return 0;
2765       case WM_KEYDOWN:
2766         {
2767             int key = -1;
2768             BYTE keystate[256];
2769             int r = GetKeyboardState(keystate);
2770             int shift = (r && (keystate[VK_SHIFT] & 0x80)) ? MOD_SHFT : 0;
2771             int ctrl = (r && (keystate[VK_CONTROL] & 0x80)) ? MOD_CTRL : 0;
2772
2773             switch (wParam) {
2774               case VK_LEFT:
2775                 if (!(lParam & 0x01000000))
2776                     key = MOD_NUM_KEYPAD | '4';
2777                 else
2778                     key = shift | ctrl | CURSOR_LEFT;
2779                 break;
2780               case VK_RIGHT:
2781                 if (!(lParam & 0x01000000))
2782                     key = MOD_NUM_KEYPAD | '6';
2783                 else
2784                     key = shift | ctrl | CURSOR_RIGHT;
2785                 break;
2786               case VK_UP:
2787                 if (!(lParam & 0x01000000))
2788                     key = MOD_NUM_KEYPAD | '8';
2789                 else
2790                     key = shift | ctrl | CURSOR_UP;
2791                 break;
2792               case VK_DOWN:
2793                 if (!(lParam & 0x01000000))
2794                     key = MOD_NUM_KEYPAD | '2';
2795                 else
2796                     key = shift | ctrl | CURSOR_DOWN;
2797                 break;
2798                 /*
2799                  * Diagonal keys on the numeric keypad.
2800                  */
2801               case VK_PRIOR:
2802                 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '9';
2803                 break;
2804               case VK_NEXT:
2805                 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '3';
2806                 break;
2807               case VK_HOME:
2808                 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '7';
2809                 break;
2810               case VK_END:
2811                 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '1';
2812                 break;
2813               case VK_INSERT:
2814                 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '0';
2815                 break;
2816               case VK_CLEAR:
2817                 if (!(lParam & 0x01000000)) key = MOD_NUM_KEYPAD | '5';
2818                 break;
2819                 /*
2820                  * Numeric keypad keys with Num Lock on.
2821                  */
2822               case VK_NUMPAD4: key = MOD_NUM_KEYPAD | '4'; break;
2823               case VK_NUMPAD6: key = MOD_NUM_KEYPAD | '6'; break;
2824               case VK_NUMPAD8: key = MOD_NUM_KEYPAD | '8'; break;
2825               case VK_NUMPAD2: key = MOD_NUM_KEYPAD | '2'; break;
2826               case VK_NUMPAD5: key = MOD_NUM_KEYPAD | '5'; break;
2827               case VK_NUMPAD9: key = MOD_NUM_KEYPAD | '9'; break;
2828               case VK_NUMPAD3: key = MOD_NUM_KEYPAD | '3'; break;
2829               case VK_NUMPAD7: key = MOD_NUM_KEYPAD | '7'; break;
2830               case VK_NUMPAD1: key = MOD_NUM_KEYPAD | '1'; break;
2831               case VK_NUMPAD0: key = MOD_NUM_KEYPAD | '0'; break;
2832             }
2833
2834             if (key != -1) {
2835                 if (!midend_process_key(fe->me, 0, 0, key))
2836                     PostQuitMessage(0);
2837             } else {
2838                 MSG m;
2839                 m.hwnd = hwnd;
2840                 m.message = WM_KEYDOWN;
2841                 m.wParam = wParam;
2842                 m.lParam = lParam & 0xdfff;
2843                 TranslateMessage(&m);
2844             }
2845         }
2846         break;
2847       case WM_LBUTTONDOWN:
2848       case WM_RBUTTONDOWN:
2849       case WM_MBUTTONDOWN:
2850         {
2851             int button;
2852
2853             /*
2854              * Shift-clicks count as middle-clicks, since otherwise
2855              * two-button Windows users won't have any kind of
2856              * middle click to use.
2857              */
2858             if (message == WM_MBUTTONDOWN || (wParam & MK_SHIFT))
2859                 button = MIDDLE_BUTTON;
2860             else if (message == WM_RBUTTONDOWN || is_alt_pressed())
2861                 button = RIGHT_BUTTON;
2862             else
2863 #ifndef _WIN32_WCE
2864                 button = LEFT_BUTTON;
2865 #else
2866                 if ((thegame.flags & REQUIRE_RBUTTON) == 0)
2867                     button = LEFT_BUTTON;
2868                 else
2869                 {
2870                     SHRGINFO shrgi;
2871
2872                     shrgi.cbSize     = sizeof(SHRGINFO);
2873                     shrgi.hwndClient = hwnd;
2874                     shrgi.ptDown.x   = (signed short)LOWORD(lParam);
2875                     shrgi.ptDown.y   = (signed short)HIWORD(lParam);
2876                     shrgi.dwFlags    = SHRG_RETURNCMD;
2877
2878                     if (GN_CONTEXTMENU == SHRecognizeGesture(&shrgi))
2879                         button = RIGHT_BUTTON;
2880                     else
2881                         button = LEFT_BUTTON;
2882                 }
2883 #endif
2884
2885             if (!midend_process_key(fe->me,
2886                                     (signed short)LOWORD(lParam) - fe->bitmapPosition.left,
2887                                     (signed short)HIWORD(lParam) - fe->bitmapPosition.top,
2888                                     button))
2889                 PostQuitMessage(0);
2890
2891             SetCapture(hwnd);
2892         }
2893         break;
2894       case WM_LBUTTONUP:
2895       case WM_RBUTTONUP:
2896       case WM_MBUTTONUP:
2897         {
2898             int button;
2899
2900             /*
2901              * Shift-clicks count as middle-clicks, since otherwise
2902              * two-button Windows users won't have any kind of
2903              * middle click to use.
2904              */
2905             if (message == WM_MBUTTONUP || (wParam & MK_SHIFT))
2906                 button = MIDDLE_RELEASE;
2907             else if (message == WM_RBUTTONUP || is_alt_pressed())
2908                 button = RIGHT_RELEASE;
2909             else
2910                 button = LEFT_RELEASE;
2911
2912             if (!midend_process_key(fe->me,
2913                                     (signed short)LOWORD(lParam) - fe->bitmapPosition.left,
2914                                     (signed short)HIWORD(lParam) - fe->bitmapPosition.top,
2915                                     button))
2916                 PostQuitMessage(0);
2917
2918             ReleaseCapture();
2919         }
2920         break;
2921       case WM_MOUSEMOVE:
2922         {
2923             int button;
2924
2925             if (wParam & (MK_MBUTTON | MK_SHIFT))
2926                 button = MIDDLE_DRAG;
2927             else if (wParam & MK_RBUTTON || is_alt_pressed())
2928                 button = RIGHT_DRAG;
2929             else
2930                 button = LEFT_DRAG;
2931             
2932             if (!midend_process_key(fe->me,
2933                                     (signed short)LOWORD(lParam) - fe->bitmapPosition.left,
2934                                     (signed short)HIWORD(lParam) - fe->bitmapPosition.top,
2935                                     button))
2936                 PostQuitMessage(0);
2937         }
2938         break;
2939       case WM_CHAR:
2940         if (!midend_process_key(fe->me, 0, 0, (unsigned char)wParam))
2941             PostQuitMessage(0);
2942         return 0;
2943       case WM_TIMER:
2944         if (fe->timer) {
2945             DWORD now = GetTickCount();
2946             float elapsed = (float) (now - fe->timer_last_tickcount) * 0.001F;
2947             midend_timer(fe->me, elapsed);
2948             fe->timer_last_tickcount = now;
2949         }
2950         return 0;
2951     }
2952
2953     return DefWindowProc(hwnd, message, wParam, lParam);
2954 }
2955
2956 #ifdef _WIN32_WCE
2957 static int FindPreviousInstance()
2958 {
2959     /* Check if application is running. If it's running then focus on the window */
2960     HWND hOtherWnd = NULL;
2961
2962     hOtherWnd = FindWindow (wGameName, wGameName);
2963     if (hOtherWnd)
2964     {
2965         SetForegroundWindow (hOtherWnd);
2966         return TRUE;
2967     }
2968
2969     return FALSE;
2970 }
2971 #endif
2972
2973 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
2974 {
2975     MSG msg;
2976     char *error;
2977
2978 #ifdef _WIN32_WCE
2979     MultiByteToWideChar (CP_ACP, 0, thegame.name, -1, wGameName, 256);
2980     if (FindPreviousInstance ())
2981         return 0;
2982 #endif
2983
2984     InitCommonControls();
2985
2986     if (!prev) {
2987         WNDCLASS wndclass;
2988
2989         wndclass.style = 0;
2990         wndclass.lpfnWndProc = WndProc;
2991         wndclass.cbClsExtra = 0;
2992         wndclass.cbWndExtra = 0;
2993         wndclass.hInstance = inst;
2994         wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(200));
2995 #ifndef _WIN32_WCE
2996         if (!wndclass.hIcon)           /* in case resource file is absent */
2997             wndclass.hIcon = LoadIcon(inst, IDI_APPLICATION);
2998 #endif
2999         wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
3000         wndclass.hbrBackground = NULL;
3001         wndclass.lpszMenuName = NULL;
3002 #ifdef _WIN32_WCE
3003         wndclass.lpszClassName = wGameName;
3004 #else
3005         wndclass.lpszClassName = thegame.name;
3006 #endif
3007
3008         RegisterClass(&wndclass);
3009     }
3010
3011     while (*cmdline && isspace((unsigned char)*cmdline))
3012         cmdline++;
3013
3014     init_help();
3015
3016     if (!new_window(inst, *cmdline ? cmdline : NULL, &error)) {
3017         char buf[128];
3018         sprintf(buf, "%.100s Error", thegame.name);
3019         MessageBox(NULL, error, buf, MB_OK|MB_ICONERROR);
3020         return 1;
3021     }
3022
3023     while (GetMessage(&msg, NULL, 0, 0)) {
3024         DispatchMessage(&msg);
3025     }
3026
3027     cleanup_help();
3028
3029     return msg.wParam;
3030 }