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