2 * windows.c: Windows front end for my puzzle collection.
16 #define IDM_NEW 0x0010
17 #define IDM_RESTART 0x0020
18 #define IDM_UNDO 0x0030
19 #define IDM_REDO 0x0040
20 #define IDM_QUIT 0x0050
21 #define IDM_PRESETS 0x0100
24 static FILE *debug_fp = NULL;
25 static HANDLE debug_hdl = INVALID_HANDLE_VALUE;
26 static int debug_got_console = 0;
32 if (!debug_got_console) {
34 debug_got_console = 1;
35 debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE);
39 debug_fp = fopen("debug.log", "w");
42 if (debug_hdl != INVALID_HANDLE_VALUE) {
43 WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL);
49 void debug_printf(char *fmt, ...)
55 vsprintf(buf, fmt, ap);
60 #define debug(x) (debug_printf x)
77 HBITMAP bitmap, prevbm;
85 game_params **presets;
90 void fatal(char *fmt, ...)
96 vsprintf(buf, fmt, ap);
99 MessageBox(NULL, buf, "Fatal error", MB_ICONEXCLAMATION | MB_OK);
104 void status_bar(frontend *fe, char *text)
106 SetWindowText(fe->statusbar, text);
109 void frontend_default_colour(frontend *fe, float *output)
111 DWORD c = GetSysColor(COLOR_MENU); /* ick */
113 output[0] = (float)(GetRValue(c) / 255.0);
114 output[1] = (float)(GetGValue(c) / 255.0);
115 output[2] = (float)(GetBValue(c) / 255.0);
118 void clip(frontend *fe, int x, int y, int w, int h)
121 fe->clip = CreateRectRgn(0, 0, 1, 1);
122 GetClipRgn(fe->hdc_bm, fe->clip);
125 IntersectClipRect(fe->hdc_bm, x, y, x+w, y+h);
128 void unclip(frontend *fe)
131 SelectClipRgn(fe->hdc_bm, fe->clip);
134 void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
135 int align, int colour, char *text)
140 * Find or create the font.
142 for (i = 0; i < fe->nfonts; i++)
143 if (fe->fonts[i].type == fonttype && fe->fonts[i].size == fontsize)
146 if (i == fe->nfonts) {
147 if (fe->fontsize <= fe->nfonts) {
148 fe->fontsize = fe->nfonts + 10;
149 fe->fonts = sresize(fe->fonts, fe->fontsize, struct font);
154 fe->fonts[i].type = fonttype;
155 fe->fonts[i].size = fontsize;
158 * FIXME: Really I should make at least _some_ effort to
159 * pick the correct font.
161 fe->fonts[i].font = CreateFont(-fontsize, 0, 0, 0, 0,
162 FALSE, FALSE, FALSE, DEFAULT_CHARSET,
163 OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
165 (fonttype == FONT_FIXED ?
166 FIXED_PITCH | FF_DONTCARE :
167 VARIABLE_PITCH | FF_SWISS),
172 * Position and draw the text.
179 oldfont = SelectObject(fe->hdc_bm, fe->fonts[i].font);
180 if (GetTextMetrics(fe->hdc_bm, &tm)) {
181 if (align & ALIGN_VCENTRE)
182 y -= (tm.tmAscent+tm.tmDescent)/2;
186 if (GetTextExtentPoint32(fe->hdc_bm, text, strlen(text), &size)) {
187 if (align & ALIGN_HCENTRE)
189 else if (align & ALIGN_HRIGHT)
192 SetBkMode(fe->hdc_bm, TRANSPARENT);
193 TextOut(fe->hdc_bm, x, y, text, strlen(text));
194 SelectObject(fe->hdc_bm, oldfont);
198 void draw_rect(frontend *fe, int x, int y, int w, int h, int colour)
200 if (w == 1 && h == 1) {
202 * Rectangle() appears to get uppity if asked to draw a 1x1
203 * rectangle, presumably on the grounds that that's beneath
204 * its dignity and you ought to be using SetPixel instead.
207 SetPixel(fe->hdc_bm, x, y, fe->colours[colour]);
209 HBRUSH oldbrush = SelectObject(fe->hdc_bm, fe->brushes[colour]);
210 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
211 Rectangle(fe->hdc_bm, x, y, x+w, y+h);
212 SelectObject(fe->hdc_bm, oldbrush);
213 SelectObject(fe->hdc_bm, oldpen);
217 void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour)
219 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
220 MoveToEx(fe->hdc_bm, x1, y1, NULL);
221 LineTo(fe->hdc_bm, x2, y2);
222 SetPixel(fe->hdc_bm, x2, y2, fe->colours[colour]);
223 SelectObject(fe->hdc_bm, oldpen);
226 void draw_polygon(frontend *fe, int *coords, int npoints,
227 int fill, int colour)
229 POINT *pts = snewn(npoints+1, POINT);
232 for (i = 0; i <= npoints; i++) {
233 int j = (i < npoints ? i : 0);
234 pts[i].x = coords[j*2];
235 pts[i].y = coords[j*2+1];
239 HBRUSH oldbrush = SelectObject(fe->hdc_bm, fe->brushes[colour]);
240 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
241 Polygon(fe->hdc_bm, pts, npoints);
242 SelectObject(fe->hdc_bm, oldbrush);
243 SelectObject(fe->hdc_bm, oldpen);
245 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
246 Polyline(fe->hdc_bm, pts, npoints+1);
247 SelectObject(fe->hdc_bm, oldpen);
253 void start_draw(frontend *fe)
256 hdc_win = GetDC(fe->hwnd);
257 fe->hdc_bm = CreateCompatibleDC(hdc_win);
258 fe->prevbm = SelectObject(fe->hdc_bm, fe->bitmap);
259 ReleaseDC(fe->hwnd, hdc_win);
263 void draw_update(frontend *fe, int x, int y, int w, int h)
272 InvalidateRect(fe->hwnd, &r, FALSE);
275 void end_draw(frontend *fe)
277 SelectObject(fe->hdc_bm, fe->prevbm);
278 DeleteDC(fe->hdc_bm);
280 DeleteObject(fe->clip);
285 void deactivate_timer(frontend *fe)
287 KillTimer(fe->hwnd, fe->timer);
291 void activate_timer(frontend *fe)
293 fe->timer = SetTimer(fe->hwnd, fe->timer, 20, NULL);
296 static frontend *new_window(HINSTANCE inst)
304 fe->me = midend_new(fe);
305 midend_new_game(fe->me, NULL);
306 midend_size(fe->me, &x, &y);
314 colours = midend_colours(fe->me, &ncolours);
316 fe->colours = snewn(ncolours, COLORREF);
317 fe->brushes = snewn(ncolours, HBRUSH);
318 fe->pens = snewn(ncolours, HPEN);
320 for (i = 0; i < ncolours; i++) {
321 fe->colours[i] = RGB(255 * colours[i*3+0],
322 255 * colours[i*3+1],
323 255 * colours[i*3+2]);
324 fe->brushes[i] = CreateSolidBrush(fe->colours[i]);
326 MessageBox(fe->hwnd, "ooh", "eck", MB_OK);
327 fe->pens[i] = CreatePen(PS_SOLID, 1, fe->colours[i]);
334 AdjustWindowRectEx(&r, WS_OVERLAPPEDWINDOW &~
335 (WS_THICKFRAME | WS_MAXIMIZEBOX | WS_OVERLAPPED),
338 fe->hwnd = CreateWindowEx(0, game_name, game_name,
339 WS_OVERLAPPEDWINDOW &~
340 (WS_THICKFRAME | WS_MAXIMIZEBOX),
341 CW_USEDEFAULT, CW_USEDEFAULT,
342 r.right - r.left, r.bottom - r.top,
343 NULL, NULL, inst, NULL);
346 HMENU bar = CreateMenu();
347 HMENU menu = CreateMenu();
349 AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)menu, "Game");
350 AppendMenu(menu, MF_ENABLED, IDM_NEW, "New");
351 AppendMenu(menu, MF_ENABLED, IDM_RESTART, "Restart");
353 if ((fe->npresets = midend_num_presets(fe->me)) > 0) {
354 HMENU sub = CreateMenu();
357 AppendMenu(menu, MF_ENABLED|MF_POPUP, (UINT)sub, "Type");
359 fe->presets = snewn(fe->npresets, game_params *);
361 for (i = 0; i < fe->npresets; i++) {
364 midend_fetch_preset(fe->me, i, &name, &fe->presets[i]);
367 * FIXME: we ought to go through and do something
368 * with ampersands here.
371 AppendMenu(sub, MF_ENABLED, IDM_PRESETS + 0x10 * i, name);
375 AppendMenu(menu, MF_SEPARATOR, 0, 0);
376 AppendMenu(menu, MF_ENABLED, IDM_UNDO, "Undo");
377 AppendMenu(menu, MF_ENABLED, IDM_REDO, "Redo");
378 AppendMenu(menu, MF_SEPARATOR, 0, 0);
379 AppendMenu(menu, MF_ENABLED, IDM_QUIT, "Exit");
380 SetMenu(fe->hwnd, bar);
383 if (midend_wants_statusbar(fe->me)) {
384 fe->statusbar = CreateWindowEx(0, STATUSCLASSNAME, "ooh",
385 WS_CHILD | WS_VISIBLE,
386 0, 0, 0, 0, /* status bar does these */
387 fe->hwnd, NULL, inst, NULL);
388 GetWindowRect(fe->statusbar, &sr);
389 SetWindowPos(fe->hwnd, NULL, 0, 0,
390 r.right - r.left, r.bottom - r.top + sr.bottom - sr.top,
391 SWP_NOMOVE | SWP_NOZORDER);
392 SetWindowPos(fe->statusbar, NULL, 0, y, x, sr.bottom - sr.top,
395 fe->statusbar = NULL;
398 hdc = GetDC(fe->hwnd);
399 fe->bitmap = CreateCompatibleBitmap(hdc, x, y);
400 ReleaseDC(fe->hwnd, hdc);
402 SetWindowLong(fe->hwnd, GWL_USERDATA, (LONG)fe);
404 ShowWindow(fe->hwnd, SW_NORMAL);
405 SetForegroundWindow(fe->hwnd);
407 midend_redraw(fe->me);
412 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
413 WPARAM wParam, LPARAM lParam)
415 frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
422 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
424 if (!midend_process_key(fe->me, 0, 0, 'n'))
428 if (!midend_process_key(fe->me, 0, 0, 'r'))
432 if (!midend_process_key(fe->me, 0, 0, 'u'))
436 if (!midend_process_key(fe->me, 0, 0, '\x12'))
440 if (!midend_process_key(fe->me, 0, 0, 'q'))
445 int p = ((wParam &~ 0xF) - IDM_PRESETS) / 0x10;
447 if (p >= 0 && p < fe->npresets) {
452 midend_set_params(fe->me, fe->presets[p]);
453 midend_new_game(fe->me, NULL);
454 midend_size(fe->me, &x, &y);
459 AdjustWindowRectEx(&r, WS_OVERLAPPEDWINDOW &~
460 (WS_THICKFRAME | WS_MAXIMIZEBOX |
464 if (fe->statusbar != NULL) {
465 GetWindowRect(fe->statusbar, &sr);
467 sr.left = sr.right = sr.top = sr.bottom = 0;
469 SetWindowPos(fe->hwnd, NULL, 0, 0,
471 r.bottom - r.top + sr.bottom - sr.top,
472 SWP_NOMOVE | SWP_NOZORDER);
473 if (fe->statusbar != NULL)
474 SetWindowPos(fe->statusbar, NULL, 0, y, x,
475 sr.bottom - sr.top, SWP_NOZORDER);
477 DeleteObject(fe->bitmap);
479 hdc = GetDC(fe->hwnd);
480 fe->bitmap = CreateCompatibleBitmap(hdc, x, y);
481 ReleaseDC(fe->hwnd, hdc);
483 midend_redraw(fe->me);
498 hdc = BeginPaint(hwnd, &p);
499 hdc2 = CreateCompatibleDC(hdc);
500 prevbm = SelectObject(hdc2, fe->bitmap);
502 p.rcPaint.left, p.rcPaint.top,
503 p.rcPaint.right - p.rcPaint.left,
504 p.rcPaint.bottom - p.rcPaint.top,
506 p.rcPaint.left, p.rcPaint.top,
508 SelectObject(hdc2, prevbm);
518 case VK_LEFT: key = CURSOR_LEFT; break;
519 case VK_RIGHT: key = CURSOR_RIGHT; break;
520 case VK_UP: key = CURSOR_UP; break;
521 case VK_DOWN: key = CURSOR_DOWN; break;
523 * Diagonal keys on the numeric keypad.
526 if (!(lParam & 0x01000000)) key = CURSOR_UP_RIGHT;
529 if (!(lParam & 0x01000000)) key = CURSOR_DOWN_RIGHT;
532 if (!(lParam & 0x01000000)) key = CURSOR_UP_LEFT;
535 if (!(lParam & 0x01000000)) key = CURSOR_DOWN_LEFT;
538 * Numeric keypad keys with Num Lock on.
540 case VK_NUMPAD4: key = CURSOR_LEFT; break;
541 case VK_NUMPAD6: key = CURSOR_RIGHT; break;
542 case VK_NUMPAD8: key = CURSOR_UP; break;
543 case VK_NUMPAD2: key = CURSOR_DOWN; break;
544 case VK_NUMPAD9: key = CURSOR_UP_RIGHT; break;
545 case VK_NUMPAD3: key = CURSOR_DOWN_RIGHT; break;
546 case VK_NUMPAD7: key = CURSOR_UP_LEFT; break;
547 case VK_NUMPAD1: key = CURSOR_DOWN_LEFT; break;
551 if (!midend_process_key(fe->me, 0, 0, key))
556 m.message = WM_KEYDOWN;
558 m.lParam = lParam & 0xdfff;
559 TranslateMessage(&m);
570 * Shift-clicks count as middle-clicks, since otherwise
571 * two-button Windows users won't have any kind of
572 * middle click to use.
574 if (message == WM_MBUTTONDOWN || (wParam & MK_SHIFT))
575 button = MIDDLE_BUTTON;
576 else if (message == WM_LBUTTONDOWN)
577 button = LEFT_BUTTON;
579 button = RIGHT_BUTTON;
581 if (!midend_process_key(fe->me, LOWORD(lParam),
582 HIWORD(lParam), button))
587 if (!midend_process_key(fe->me, 0, 0, (unsigned char)wParam))
592 midend_timer(fe->me, (float)0.02);
596 return DefWindowProc(hwnd, message, wParam, lParam);
599 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
605 InitCommonControls();
611 wndclass.lpfnWndProc = WndProc;
612 wndclass.cbClsExtra = 0;
613 wndclass.cbWndExtra = 0;
614 wndclass.hInstance = inst;
615 wndclass.hIcon = LoadIcon(inst, IDI_APPLICATION);
616 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
617 wndclass.hbrBackground = NULL;
618 wndclass.lpszMenuName = NULL;
619 wndclass.lpszClassName = game_name;
621 RegisterClass(&wndclass);
626 while (GetMessage(&msg, NULL, 0, 0)) {
627 DispatchMessage(&msg);