2 * windows.c: Windows front end for my puzzle collection.
15 #define IDM_NEW 0x0010
16 #define IDM_RESTART 0x0020
17 #define IDM_UNDO 0x0030
18 #define IDM_REDO 0x0040
19 #define IDM_QUIT 0x0050
20 #define IDM_PRESETS 0x0100
23 static FILE *debug_fp
= NULL
;
24 static HANDLE debug_hdl
= INVALID_HANDLE_VALUE
;
25 static int debug_got_console
= 0;
31 if (!debug_got_console
) {
33 debug_got_console
= 1;
34 debug_hdl
= GetStdHandle(STD_OUTPUT_HANDLE
);
38 debug_fp
= fopen("debug.log", "w");
41 if (debug_hdl
!= INVALID_HANDLE_VALUE
) {
42 WriteFile(debug_hdl
, buf
, strlen(buf
), &dw
, NULL
);
48 void debug_printf(char *fmt
, ...)
54 vsprintf(buf
, fmt
, ap
);
59 #define debug(x) (debug_printf x)
76 HBITMAP bitmap
, prevbm
;
84 game_params
**presets
;
89 void fatal(char *fmt
, ...)
95 vsprintf(buf
, fmt
, ap
);
98 MessageBox(NULL
, buf
, "Fatal error", MB_ICONEXCLAMATION
| MB_OK
);
103 void frontend_default_colour(frontend
*fe
, float *output
)
105 DWORD c
= GetSysColor(COLOR_MENU
); /* ick */
107 output
[0] = (float)(GetRValue(c
) / 255.0);
108 output
[1] = (float)(GetGValue(c
) / 255.0);
109 output
[2] = (float)(GetBValue(c
) / 255.0);
112 void clip(frontend
*fe
, int x
, int y
, int w
, int h
)
115 fe
->clip
= CreateRectRgn(0, 0, 1, 1);
116 GetClipRgn(fe
->hdc_bm
, fe
->clip
);
119 IntersectClipRect(fe
->hdc_bm
, x
, y
, x
+w
, y
+h
);
122 void unclip(frontend
*fe
)
125 SelectClipRgn(fe
->hdc_bm
, fe
->clip
);
128 void draw_text(frontend
*fe
, int x
, int y
, int fonttype
, int fontsize
,
129 int align
, int colour
, char *text
)
134 * Find or create the font.
136 for (i
= 0; i
< fe
->nfonts
; i
++)
137 if (fe
->fonts
[i
].type
== fonttype
&& fe
->fonts
[i
].size
== fontsize
)
140 if (i
== fe
->nfonts
) {
141 if (fe
->fontsize
<= fe
->nfonts
) {
142 fe
->fontsize
= fe
->nfonts
+ 10;
143 fe
->fonts
= sresize(fe
->fonts
, fe
->fontsize
, struct font
);
148 fe
->fonts
[i
].type
= fonttype
;
149 fe
->fonts
[i
].size
= fontsize
;
152 * FIXME: Really I should make at least _some_ effort to
153 * pick the correct font.
155 fe
->fonts
[i
].font
= CreateFont(-fontsize
, 0, 0, 0, 0,
156 FALSE
, FALSE
, FALSE
, DEFAULT_CHARSET
,
157 OUT_DEFAULT_PRECIS
, CLIP_DEFAULT_PRECIS
,
159 (fonttype
== FONT_FIXED ?
160 FIXED_PITCH
| FF_DONTCARE
:
161 VARIABLE_PITCH
| FF_SWISS
),
166 * Position and draw the text.
173 oldfont
= SelectObject(fe
->hdc_bm
, fe
->fonts
[i
].font
);
174 if (GetTextMetrics(fe
->hdc_bm
, &tm
)) {
175 if (align
& ALIGN_VCENTRE
)
176 y
-= (tm
.tmAscent
+tm
.tmDescent
)/2;
180 if (GetTextExtentPoint32(fe
->hdc_bm
, text
, strlen(text
), &size
)) {
181 if (align
& ALIGN_HCENTRE
)
183 else if (align
& ALIGN_HRIGHT
)
186 SetBkMode(fe
->hdc_bm
, TRANSPARENT
);
187 TextOut(fe
->hdc_bm
, x
, y
, text
, strlen(text
));
188 SelectObject(fe
->hdc_bm
, oldfont
);
192 void draw_rect(frontend
*fe
, int x
, int y
, int w
, int h
, int colour
)
194 if (w
== 1 && h
== 1) {
196 * Rectangle() appears to get uppity if asked to draw a 1x1
197 * rectangle, presumably on the grounds that that's beneath
198 * its dignity and you ought to be using SetPixel instead.
201 SetPixel(fe
->hdc_bm
, x
, y
, fe
->colours
[colour
]);
203 HBRUSH oldbrush
= SelectObject(fe
->hdc_bm
, fe
->brushes
[colour
]);
204 HPEN oldpen
= SelectObject(fe
->hdc_bm
, fe
->pens
[colour
]);
205 Rectangle(fe
->hdc_bm
, x
, y
, x
+w
, y
+h
);
206 SelectObject(fe
->hdc_bm
, oldbrush
);
207 SelectObject(fe
->hdc_bm
, oldpen
);
211 void draw_line(frontend
*fe
, int x1
, int y1
, int x2
, int y2
, int colour
)
213 HPEN oldpen
= SelectObject(fe
->hdc_bm
, fe
->pens
[colour
]);
214 MoveToEx(fe
->hdc_bm
, x1
, y1
, NULL
);
215 LineTo(fe
->hdc_bm
, x2
, y2
);
216 SetPixel(fe
->hdc_bm
, x2
, y2
, fe
->colours
[colour
]);
217 SelectObject(fe
->hdc_bm
, oldpen
);
220 void draw_polygon(frontend
*fe
, int *coords
, int npoints
,
221 int fill
, int colour
)
223 POINT
*pts
= snewn(npoints
+1, POINT
);
226 for (i
= 0; i
<= npoints
; i
++) {
227 int j
= (i
< npoints ? i
: 0);
228 pts
[i
].x
= coords
[j
*2];
229 pts
[i
].y
= coords
[j
*2+1];
233 HBRUSH oldbrush
= SelectObject(fe
->hdc_bm
, fe
->brushes
[colour
]);
234 HPEN oldpen
= SelectObject(fe
->hdc_bm
, fe
->pens
[colour
]);
235 Polygon(fe
->hdc_bm
, pts
, npoints
);
236 SelectObject(fe
->hdc_bm
, oldbrush
);
237 SelectObject(fe
->hdc_bm
, oldpen
);
239 HPEN oldpen
= SelectObject(fe
->hdc_bm
, fe
->pens
[colour
]);
240 Polyline(fe
->hdc_bm
, pts
, npoints
+1);
241 SelectObject(fe
->hdc_bm
, oldpen
);
247 void start_draw(frontend
*fe
)
250 hdc_win
= GetDC(fe
->hwnd
);
251 fe
->hdc_bm
= CreateCompatibleDC(hdc_win
);
252 fe
->prevbm
= SelectObject(fe
->hdc_bm
, fe
->bitmap
);
253 ReleaseDC(fe
->hwnd
, hdc_win
);
257 void draw_update(frontend
*fe
, int x
, int y
, int w
, int h
)
266 InvalidateRect(fe
->hwnd
, &r
, FALSE
);
269 void end_draw(frontend
*fe
)
271 SelectObject(fe
->hdc_bm
, fe
->prevbm
);
272 DeleteDC(fe
->hdc_bm
);
274 DeleteObject(fe
->clip
);
279 void deactivate_timer(frontend
*fe
)
281 KillTimer(fe
->hwnd
, fe
->timer
);
285 void activate_timer(frontend
*fe
)
287 fe
->timer
= SetTimer(fe
->hwnd
, fe
->timer
, 20, NULL
);
290 static frontend
*new_window(HINSTANCE inst
)
298 fe
->me
= midend_new(fe
);
299 midend_new_game(fe
->me
, NULL
);
300 midend_size(fe
->me
, &x
, &y
);
308 colours
= midend_colours(fe
->me
, &ncolours
);
310 fe
->colours
= snewn(ncolours
, COLORREF
);
311 fe
->brushes
= snewn(ncolours
, HBRUSH
);
312 fe
->pens
= snewn(ncolours
, HPEN
);
314 for (i
= 0; i
< ncolours
; i
++) {
315 fe
->colours
[i
] = RGB(255 * colours
[i
*3+0],
316 255 * colours
[i
*3+1],
317 255 * colours
[i
*3+2]);
318 fe
->brushes
[i
] = CreateSolidBrush(fe
->colours
[i
]);
320 MessageBox(fe
->hwnd
, "ooh", "eck", MB_OK
);
321 fe
->pens
[i
] = CreatePen(PS_SOLID
, 1, fe
->colours
[i
]);
328 AdjustWindowRectEx(&r
, WS_OVERLAPPEDWINDOW
&~
329 (WS_THICKFRAME
| WS_MAXIMIZEBOX
| WS_OVERLAPPED
),
332 fe
->hwnd
= CreateWindowEx(0, game_name
, game_name
,
333 WS_OVERLAPPEDWINDOW
&~
334 (WS_THICKFRAME
| WS_MAXIMIZEBOX
),
335 CW_USEDEFAULT
, CW_USEDEFAULT
,
336 r
.right
- r
.left
, r
.bottom
- r
.top
,
337 NULL
, NULL
, inst
, NULL
);
340 HMENU bar
= CreateMenu();
341 HMENU menu
= CreateMenu();
343 AppendMenu(bar
, MF_ENABLED
|MF_POPUP
, (UINT
)menu
, "Game");
344 AppendMenu(menu
, MF_ENABLED
, IDM_NEW
, "New");
345 AppendMenu(menu
, MF_ENABLED
, IDM_RESTART
, "Restart");
347 if ((fe
->npresets
= midend_num_presets(fe
->me
)) > 0) {
348 HMENU sub
= CreateMenu();
351 AppendMenu(menu
, MF_ENABLED
|MF_POPUP
, (UINT
)sub
, "Type");
353 fe
->presets
= snewn(fe
->npresets
, game_params
*);
355 for (i
= 0; i
< fe
->npresets
; i
++) {
358 midend_fetch_preset(fe
->me
, i
, &name
, &fe
->presets
[i
]);
361 * FIXME: we ought to go through and do something
362 * with ampersands here.
365 AppendMenu(sub
, MF_ENABLED
, IDM_PRESETS
+ 0x10 * i
, name
);
369 AppendMenu(menu
, MF_SEPARATOR
, 0, 0);
370 AppendMenu(menu
, MF_ENABLED
, IDM_UNDO
, "Undo");
371 AppendMenu(menu
, MF_ENABLED
, IDM_REDO
, "Redo");
372 AppendMenu(menu
, MF_SEPARATOR
, 0, 0);
373 AppendMenu(menu
, MF_ENABLED
, IDM_QUIT
, "Exit");
374 SetMenu(fe
->hwnd
, bar
);
377 hdc
= GetDC(fe
->hwnd
);
378 fe
->bitmap
= CreateCompatibleBitmap(hdc
, x
, y
);
379 ReleaseDC(fe
->hwnd
, hdc
);
381 SetWindowLong(fe
->hwnd
, GWL_USERDATA
, (LONG
)fe
);
383 ShowWindow(fe
->hwnd
, SW_NORMAL
);
384 SetForegroundWindow(fe
->hwnd
);
386 midend_redraw(fe
->me
);
391 static LRESULT CALLBACK
WndProc(HWND hwnd
, UINT message
,
392 WPARAM wParam
, LPARAM lParam
)
394 frontend
*fe
= (frontend
*)GetWindowLong(hwnd
, GWL_USERDATA
);
401 switch (wParam
& ~0xF) { /* low 4 bits reserved to Windows */
403 if (!midend_process_key(fe
->me
, 0, 0, 'n'))
407 if (!midend_process_key(fe
->me
, 0, 0, 'r'))
411 if (!midend_process_key(fe
->me
, 0, 0, 'u'))
415 if (!midend_process_key(fe
->me
, 0, 0, '\x12'))
419 if (!midend_process_key(fe
->me
, 0, 0, 'q'))
424 int p
= ((wParam
&~ 0xF) - IDM_PRESETS
) / 0x10;
426 if (p
>= 0 && p
< fe
->npresets
) {
431 midend_set_params(fe
->me
, fe
->presets
[p
]);
432 midend_new_game(fe
->me
, NULL
);
433 midend_size(fe
->me
, &x
, &y
);
438 AdjustWindowRectEx(&r
, WS_OVERLAPPEDWINDOW
&~
439 (WS_THICKFRAME
| WS_MAXIMIZEBOX
|
443 SetWindowPos(fe
->hwnd
, NULL
, 0, 0,
444 r
.right
- r
.left
, r
.bottom
- r
.top
,
445 SWP_NOMOVE
| SWP_NOZORDER
);
447 DeleteObject(fe
->bitmap
);
449 hdc
= GetDC(fe
->hwnd
);
450 fe
->bitmap
= CreateCompatibleBitmap(hdc
, x
, y
);
451 ReleaseDC(fe
->hwnd
, hdc
);
453 midend_redraw(fe
->me
);
468 hdc
= BeginPaint(hwnd
, &p
);
469 hdc2
= CreateCompatibleDC(hdc
);
470 prevbm
= SelectObject(hdc2
, fe
->bitmap
);
472 p
.rcPaint
.left
, p
.rcPaint
.top
,
473 p
.rcPaint
.right
- p
.rcPaint
.left
,
474 p
.rcPaint
.bottom
- p
.rcPaint
.top
,
476 p
.rcPaint
.left
, p
.rcPaint
.top
,
478 SelectObject(hdc2
, prevbm
);
488 case VK_LEFT
: key
= CURSOR_LEFT
; break;
489 case VK_RIGHT
: key
= CURSOR_RIGHT
; break;
490 case VK_UP
: key
= CURSOR_UP
; break;
491 case VK_DOWN
: key
= CURSOR_DOWN
; break;
493 * Diagonal keys on the numeric keypad.
496 if (!(lParam
& 0x01000000)) key
= CURSOR_UP_RIGHT
;
499 if (!(lParam
& 0x01000000)) key
= CURSOR_DOWN_RIGHT
;
502 if (!(lParam
& 0x01000000)) key
= CURSOR_UP_LEFT
;
505 if (!(lParam
& 0x01000000)) key
= CURSOR_DOWN_LEFT
;
508 * Numeric keypad keys with Num Lock on.
510 case VK_NUMPAD4
: key
= CURSOR_LEFT
; break;
511 case VK_NUMPAD6
: key
= CURSOR_RIGHT
; break;
512 case VK_NUMPAD8
: key
= CURSOR_UP
; break;
513 case VK_NUMPAD2
: key
= CURSOR_DOWN
; break;
514 case VK_NUMPAD9
: key
= CURSOR_UP_RIGHT
; break;
515 case VK_NUMPAD3
: key
= CURSOR_DOWN_RIGHT
; break;
516 case VK_NUMPAD7
: key
= CURSOR_UP_LEFT
; break;
517 case VK_NUMPAD1
: key
= CURSOR_DOWN_LEFT
; break;
521 if (!midend_process_key(fe
->me
, 0, 0, key
))
526 m
.message
= WM_KEYDOWN
;
528 m
.lParam
= lParam
& 0xdfff;
529 TranslateMessage(&m
);
540 * Shift-clicks count as middle-clicks, since otherwise
541 * two-button Windows users won't have any kind of
542 * middle click to use.
544 if (message
== WM_MBUTTONDOWN
|| (wParam
& MK_SHIFT
))
545 button
= MIDDLE_BUTTON
;
546 else if (message
== WM_LBUTTONDOWN
)
547 button
= LEFT_BUTTON
;
549 button
= RIGHT_BUTTON
;
551 if (!midend_process_key(fe
->me
, LOWORD(lParam
),
552 HIWORD(lParam
), button
))
557 if (!midend_process_key(fe
->me
, 0, 0, (unsigned char)wParam
))
562 midend_timer(fe
->me
, (float)0.02);
566 return DefWindowProc(hwnd
, message
, wParam
, lParam
);
569 int WINAPI
WinMain(HINSTANCE inst
, HINSTANCE prev
, LPSTR cmdline
, int show
)
579 wndclass
.lpfnWndProc
= WndProc
;
580 wndclass
.cbClsExtra
= 0;
581 wndclass
.cbWndExtra
= 0;
582 wndclass
.hInstance
= inst
;
583 wndclass
.hIcon
= LoadIcon(inst
, IDI_APPLICATION
);
584 wndclass
.hCursor
= LoadCursor(NULL
, IDC_ARROW
);
585 wndclass
.hbrBackground
= NULL
;
586 wndclass
.lpszMenuName
= NULL
;
587 wndclass
.lpszClassName
= game_name
;
589 RegisterClass(&wndclass
);
594 while (GetMessage(&msg
, NULL
, 0, 0)) {
595 DispatchMessage(&msg
);