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
);