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