Add a menu bar, in both Windows and GTK. In particular, game modules
[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 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 }