chiark / gitweb /
Shift-click is equivalent to middle-click. This is mostly for
[sgt-puzzles.git] / 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         {
442             int button;
443
444             /*
445              * Shift-clicks count as middle-clicks, since otherwise
446              * two-button Windows users won't have any kind of
447              * middle click to use.
448              */
449             if (message == WM_MBUTTONDOWN || (wParam & MK_SHIFT))
450                 button = MIDDLE_BUTTON;
451             else if (message == WM_LBUTTONDOWN)
452                 button = LEFT_BUTTON;
453             else
454                 button = RIGHT_BUTTON;
455                 
456             if (!midend_process_key(fe->me, LOWORD(lParam),
457                                     HIWORD(lParam), button))
458                 PostQuitMessage(0);
459         }
460         break;
461       case WM_CHAR:
462         if (!midend_process_key(fe->me, 0, 0, (unsigned char)wParam))
463             PostQuitMessage(0);
464         return 0;
465       case WM_TIMER:
466         if (fe->timer)
467             midend_timer(fe->me, (float)0.02);
468         return 0;
469     }
470
471     return DefWindowProc(hwnd, message, wParam, lParam);
472 }
473
474 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
475 {
476     MSG msg;
477
478     srand(time(NULL));
479
480     if (!prev) {
481         WNDCLASS wndclass;
482
483         wndclass.style = 0;
484         wndclass.lpfnWndProc = WndProc;
485         wndclass.cbClsExtra = 0;
486         wndclass.cbWndExtra = 0;
487         wndclass.hInstance = inst;
488         wndclass.hIcon = LoadIcon(inst, IDI_APPLICATION);
489         wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
490         wndclass.hbrBackground = NULL;
491         wndclass.lpszMenuName = NULL;
492         wndclass.lpszClassName = "puzzle";
493
494         RegisterClass(&wndclass);
495     }
496
497     new_window(inst);
498
499     while (GetMessage(&msg, NULL, 0, 0)) {
500         DispatchMessage(&msg);
501     }
502
503     return msg.wParam;
504 }