Introduce diagonal movement keys on the numeric keypad, and use them
[sgt/puzzles] / windows.c
1 /*
2 * windows.c: Windows front end for my puzzle collection.
3 */
4
5 #include <windows.h>
6
7 #include <stdio.h>
8 #include <stdarg.h>
9 #include <stdlib.h>
10 #include <time.h>
11
12 #include "puzzles.h"
13
14 #define IDM_NEW 0x0010
15 #define IDM_RESTART 0x0020
16 #define IDM_UNDO 0x0030
17 #define IDM_REDO 0x0040
18 #define IDM_QUIT 0x0050
19 #define IDM_PRESETS 0x0100
20
21 #ifdef DEBUG
22 static FILE *debug_fp = NULL;
23 static HANDLE debug_hdl = INVALID_HANDLE_VALUE;
24 static int debug_got_console = 0;
25
26 void dputs(char *buf)
27 {
28 DWORD dw;
29
30 if (!debug_got_console) {
31 if (AllocConsole()) {
32 debug_got_console = 1;
33 debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE);
34 }
35 }
36 if (!debug_fp) {
37 debug_fp = fopen("debug.log", "w");
38 }
39
40 if (debug_hdl != INVALID_HANDLE_VALUE) {
41 WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL);
42 }
43 fputs(buf, debug_fp);
44 fflush(debug_fp);
45 }
46
47 void debug_printf(char *fmt, ...)
48 {
49 char buf[4096];
50 va_list ap;
51
52 va_start(ap, fmt);
53 vsprintf(buf, fmt, ap);
54 dputs(buf);
55 va_end(ap);
56 }
57
58 #define debug(x) (debug_printf x)
59
60 #else
61
62 #define debug(x)
63
64 #endif
65
66 struct frontend {
67 midend_data *me;
68 HWND hwnd;
69 HBITMAP bitmap, prevbm;
70 HDC hdc_bm;
71 COLORREF *colours;
72 HBRUSH *brushes;
73 HPEN *pens;
74 UINT timer;
75 int npresets;
76 game_params **presets;
77 };
78
79 void fatal(char *fmt, ...)
80 {
81 char buf[2048];
82 va_list ap;
83
84 va_start(ap, fmt);
85 vsprintf(buf, fmt, ap);
86 va_end(ap);
87
88 MessageBox(NULL, buf, "Fatal error", MB_ICONEXCLAMATION | MB_OK);
89
90 exit(1);
91 }
92
93 void frontend_default_colour(frontend *fe, float *output)
94 {
95 DWORD c = GetSysColor(COLOR_MENU); /* ick */
96
97 output[0] = (float)(GetRValue(c) / 255.0);
98 output[1] = (float)(GetGValue(c) / 255.0);
99 output[2] = (float)(GetBValue(c) / 255.0);
100 }
101
102 void draw_rect(frontend *fe, int x, int y, int w, int h, int colour)
103 {
104 if (w == 1 && h == 1) {
105 /*
106 * Rectangle() appears to get uppity if asked to draw a 1x1
107 * rectangle, presumably on the grounds that that's beneath
108 * its dignity and you ought to be using SetPixel instead.
109 * So I will.
110 */
111 SetPixel(fe->hdc_bm, x, y, fe->colours[colour]);
112 } else {
113 HBRUSH oldbrush = SelectObject(fe->hdc_bm, fe->brushes[colour]);
114 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
115 Rectangle(fe->hdc_bm, x, y, x+w, y+h);
116 SelectObject(fe->hdc_bm, oldbrush);
117 SelectObject(fe->hdc_bm, oldpen);
118 }
119 }
120
121 void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour)
122 {
123 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
124 MoveToEx(fe->hdc_bm, x1, y1, NULL);
125 LineTo(fe->hdc_bm, x2, y2);
126 SetPixel(fe->hdc_bm, x2, y2, fe->colours[colour]);
127 SelectObject(fe->hdc_bm, oldpen);
128 }
129
130 void draw_polygon(frontend *fe, int *coords, int npoints,
131 int fill, int colour)
132 {
133 POINT *pts = snewn(npoints+1, POINT);
134 int i;
135
136 for (i = 0; i <= npoints; i++) {
137 int j = (i < npoints ? i : 0);
138 pts[i].x = coords[j*2];
139 pts[i].y = coords[j*2+1];
140 }
141
142 if (fill) {
143 HBRUSH oldbrush = SelectObject(fe->hdc_bm, fe->brushes[colour]);
144 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
145 Polygon(fe->hdc_bm, pts, npoints);
146 SelectObject(fe->hdc_bm, oldbrush);
147 SelectObject(fe->hdc_bm, oldpen);
148 } else {
149 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
150 Polyline(fe->hdc_bm, pts, npoints+1);
151 SelectObject(fe->hdc_bm, oldpen);
152 }
153
154 sfree(pts);
155 }
156
157 void start_draw(frontend *fe)
158 {
159 HDC hdc_win;
160 hdc_win = GetDC(fe->hwnd);
161 fe->hdc_bm = CreateCompatibleDC(hdc_win);
162 fe->prevbm = SelectObject(fe->hdc_bm, fe->bitmap);
163 ReleaseDC(fe->hwnd, hdc_win);
164 }
165
166 void draw_update(frontend *fe, int x, int y, int w, int h)
167 {
168 RECT r;
169
170 r.left = x;
171 r.top = y;
172 r.right = x + w;
173 r.bottom = y + h;
174
175 InvalidateRect(fe->hwnd, &r, FALSE);
176 }
177
178 void end_draw(frontend *fe)
179 {
180 SelectObject(fe->hdc_bm, fe->prevbm);
181 DeleteDC(fe->hdc_bm);
182 }
183
184 void deactivate_timer(frontend *fe)
185 {
186 KillTimer(fe->hwnd, fe->timer);
187 fe->timer = 0;
188 }
189
190 void activate_timer(frontend *fe)
191 {
192 fe->timer = SetTimer(fe->hwnd, fe->timer, 20, NULL);
193 }
194
195 static frontend *new_window(HINSTANCE inst)
196 {
197 frontend *fe;
198 int x, y;
199 RECT r;
200 HDC hdc;
201
202 fe = snew(frontend);
203 fe->me = midend_new(fe);
204 midend_new_game(fe->me, NULL);
205 midend_size(fe->me, &x, &y);
206
207 fe->timer = 0;
208
209 {
210 int i, ncolours;
211 float *colours;
212
213 colours = midend_colours(fe->me, &ncolours);
214
215 fe->colours = snewn(ncolours, COLORREF);
216 fe->brushes = snewn(ncolours, HBRUSH);
217 fe->pens = snewn(ncolours, HPEN);
218
219 for (i = 0; i < ncolours; i++) {
220 fe->colours[i] = RGB(255 * colours[i*3+0],
221 255 * colours[i*3+1],
222 255 * colours[i*3+2]);
223 fe->brushes[i] = CreateSolidBrush(fe->colours[i]);
224 if (!fe->brushes[i])
225 MessageBox(fe->hwnd, "ooh", "eck", MB_OK);
226 fe->pens[i] = CreatePen(PS_SOLID, 1, fe->colours[i]);
227 }
228 }
229
230 r.left = r.top = 0;
231 r.right = x;
232 r.bottom = y;
233 AdjustWindowRectEx(&r, WS_OVERLAPPEDWINDOW &~
234 (WS_THICKFRAME | WS_MAXIMIZEBOX | WS_OVERLAPPED),
235 TRUE, 0);
236
237 fe->hwnd = CreateWindowEx(0, "puzzle", "puzzle",
238 WS_OVERLAPPEDWINDOW &~
239 (WS_THICKFRAME | WS_MAXIMIZEBOX),
240 CW_USEDEFAULT, CW_USEDEFAULT,
241 r.right - r.left, r.bottom - r.top,
242 NULL, NULL, inst, NULL);
243
244 {
245 HMENU bar = CreateMenu();
246 HMENU menu = CreateMenu();
247
248 AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)menu, "Game");
249 AppendMenu(menu, MF_ENABLED, IDM_NEW, "New");
250 AppendMenu(menu, MF_ENABLED, IDM_RESTART, "Restart");
251
252 if ((fe->npresets = midend_num_presets(fe->me)) > 0) {
253 HMENU sub = CreateMenu();
254 int i;
255
256 AppendMenu(menu, MF_ENABLED|MF_POPUP, (UINT)sub, "Type");
257
258 fe->presets = snewn(fe->npresets, game_params *);
259
260 for (i = 0; i < fe->npresets; i++) {
261 char *name;
262
263 midend_fetch_preset(fe->me, i, &name, &fe->presets[i]);
264
265 /*
266 * FIXME: we ought to go through and do something
267 * with ampersands here.
268 */
269
270 AppendMenu(sub, MF_ENABLED, IDM_PRESETS + 0x10 * i, name);
271 }
272 }
273
274 AppendMenu(menu, MF_SEPARATOR, 0, 0);
275 AppendMenu(menu, MF_ENABLED, IDM_UNDO, "Undo");
276 AppendMenu(menu, MF_ENABLED, IDM_REDO, "Redo");
277 AppendMenu(menu, MF_SEPARATOR, 0, 0);
278 AppendMenu(menu, MF_ENABLED, IDM_QUIT, "Exit");
279 SetMenu(fe->hwnd, bar);
280 }
281
282 hdc = GetDC(fe->hwnd);
283 fe->bitmap = CreateCompatibleBitmap(hdc, x, y);
284 ReleaseDC(fe->hwnd, hdc);
285
286 SetWindowLong(fe->hwnd, GWL_USERDATA, (LONG)fe);
287
288 ShowWindow(fe->hwnd, SW_NORMAL);
289 SetForegroundWindow(fe->hwnd);
290
291 midend_redraw(fe->me);
292
293 return fe;
294 }
295
296 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
297 WPARAM wParam, LPARAM lParam)
298 {
299 frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
300
301 switch (message) {
302 case WM_CLOSE:
303 DestroyWindow(hwnd);
304 return 0;
305 case WM_COMMAND:
306 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
307 case IDM_NEW:
308 if (!midend_process_key(fe->me, 0, 0, 'n'))
309 PostQuitMessage(0);
310 break;
311 case IDM_RESTART:
312 if (!midend_process_key(fe->me, 0, 0, 'r'))
313 PostQuitMessage(0);
314 break;
315 case IDM_UNDO:
316 if (!midend_process_key(fe->me, 0, 0, 'u'))
317 PostQuitMessage(0);
318 break;
319 case IDM_REDO:
320 if (!midend_process_key(fe->me, 0, 0, '\x12'))
321 PostQuitMessage(0);
322 break;
323 case IDM_QUIT:
324 if (!midend_process_key(fe->me, 0, 0, 'q'))
325 PostQuitMessage(0);
326 break;
327 default:
328 {
329 int p = ((wParam &~ 0xF) - IDM_PRESETS) / 0x10;
330
331 if (p >= 0 && p < fe->npresets) {
332 RECT r;
333 HDC hdc;
334 int x, y;
335
336 midend_set_params(fe->me, fe->presets[p]);
337 midend_new_game(fe->me, NULL);
338 midend_size(fe->me, &x, &y);
339
340 r.left = r.top = 0;
341 r.right = x;
342 r.bottom = y;
343 AdjustWindowRectEx(&r, WS_OVERLAPPEDWINDOW &~
344 (WS_THICKFRAME | WS_MAXIMIZEBOX |
345 WS_OVERLAPPED),
346 TRUE, 0);
347
348 SetWindowPos(fe->hwnd, NULL, 0, 0,
349 r.right - r.left, r.bottom - r.top,
350 SWP_NOMOVE | SWP_NOZORDER);
351
352 DeleteObject(fe->bitmap);
353
354 hdc = GetDC(fe->hwnd);
355 fe->bitmap = CreateCompatibleBitmap(hdc, x, y);
356 ReleaseDC(fe->hwnd, hdc);
357
358 midend_redraw(fe->me);
359 }
360 }
361 break;
362 }
363 break;
364 case WM_DESTROY:
365 PostQuitMessage(0);
366 return 0;
367 case WM_PAINT:
368 {
369 PAINTSTRUCT p;
370 HDC hdc, hdc2;
371 HBITMAP prevbm;
372
373 hdc = BeginPaint(hwnd, &p);
374 hdc2 = CreateCompatibleDC(hdc);
375 prevbm = SelectObject(hdc2, fe->bitmap);
376 BitBlt(hdc,
377 p.rcPaint.left, p.rcPaint.top,
378 p.rcPaint.right - p.rcPaint.left,
379 p.rcPaint.bottom - p.rcPaint.top,
380 hdc2,
381 p.rcPaint.left, p.rcPaint.top,
382 SRCCOPY);
383 SelectObject(hdc2, prevbm);
384 DeleteDC(hdc2);
385 EndPaint(hwnd, &p);
386 }
387 return 0;
388 case WM_KEYDOWN:
389 {
390 int key = -1;
391
392 switch (wParam) {
393 case VK_LEFT: key = CURSOR_LEFT; break;
394 case VK_RIGHT: key = CURSOR_RIGHT; break;
395 case VK_UP: key = CURSOR_UP; break;
396 case VK_DOWN: key = CURSOR_DOWN; break;
397 /*
398 * Diagonal keys on the numeric keypad.
399 */
400 case VK_PRIOR:
401 if (!(lParam & 0x01000000)) key = CURSOR_UP_RIGHT;
402 break;
403 case VK_NEXT:
404 if (!(lParam & 0x01000000)) key = CURSOR_DOWN_RIGHT;
405 break;
406 case VK_HOME:
407 if (!(lParam & 0x01000000)) key = CURSOR_UP_LEFT;
408 break;
409 case VK_END:
410 if (!(lParam & 0x01000000)) key = CURSOR_DOWN_LEFT;
411 break;
412 /*
413 * Numeric keypad keys with Num Lock on.
414 */
415 case VK_NUMPAD4: key = CURSOR_LEFT; break;
416 case VK_NUMPAD6: key = CURSOR_RIGHT; break;
417 case VK_NUMPAD8: key = CURSOR_UP; break;
418 case VK_NUMPAD2: key = CURSOR_DOWN; break;
419 case VK_NUMPAD9: key = CURSOR_UP_RIGHT; break;
420 case VK_NUMPAD3: key = CURSOR_DOWN_RIGHT; break;
421 case VK_NUMPAD7: key = CURSOR_UP_LEFT; break;
422 case VK_NUMPAD1: key = CURSOR_DOWN_LEFT; break;
423 }
424
425 if (key != -1) {
426 if (!midend_process_key(fe->me, 0, 0, key))
427 PostQuitMessage(0);
428 } else {
429 MSG m;
430 m.hwnd = hwnd;
431 m.message = WM_KEYDOWN;
432 m.wParam = wParam;
433 m.lParam = lParam & 0xdfff;
434 TranslateMessage(&m);
435 }
436 }
437 break;
438 case WM_LBUTTONDOWN:
439 case WM_RBUTTONDOWN:
440 case WM_MBUTTONDOWN:
441 if (!midend_process_key(fe->me, LOWORD(lParam), HIWORD(lParam),
442 (message == WM_LBUTTONDOWN ? LEFT_BUTTON :
443 message == WM_RBUTTONDOWN ? RIGHT_BUTTON :
444 MIDDLE_BUTTON)))
445 PostQuitMessage(0);
446
447 break;
448 case WM_CHAR:
449 if (!midend_process_key(fe->me, 0, 0, (unsigned char)wParam))
450 PostQuitMessage(0);
451 return 0;
452 case WM_TIMER:
453 if (fe->timer)
454 midend_timer(fe->me, (float)0.02);
455 return 0;
456 }
457
458 return DefWindowProc(hwnd, message, wParam, lParam);
459 }
460
461 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
462 {
463 MSG msg;
464
465 srand(time(NULL));
466
467 if (!prev) {
468 WNDCLASS wndclass;
469
470 wndclass.style = 0;
471 wndclass.lpfnWndProc = WndProc;
472 wndclass.cbClsExtra = 0;
473 wndclass.cbWndExtra = 0;
474 wndclass.hInstance = inst;
475 wndclass.hIcon = LoadIcon(inst, IDI_APPLICATION);
476 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
477 wndclass.hbrBackground = NULL;
478 wndclass.lpszMenuName = NULL;
479 wndclass.lpszClassName = "puzzle";
480
481 RegisterClass(&wndclass);
482 }
483
484 new_window(inst);
485
486 while (GetMessage(&msg, NULL, 0, 0)) {
487 DispatchMessage(&msg);
488 }
489
490 return msg.wParam;
491 }