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