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