summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/windows.c
diff options
context:
space:
mode:
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: */