1 /* NetHack 3.6	mhstatus.c	$NHDT-Date: 1536411224 2018/09/08 12:53:44 $  $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.29 $ */
2 /* Copyright (C) 2001 by Alex Kompel 	 */
3 /* NetHack may be freely redistributed.  See license for details. */
4 
5 #include <assert.h>
6 #include "winos.h"
7 #include "winMS.h"
8 #include "mhstatus.h"
9 #include "mhmsg.h"
10 #include "mhfont.h"
11 
12 #ifndef STATUS_HILITES
13 #error STATUS_HILITES not defined
14 #endif
15 
16 #define MAXWINDOWTEXT BUFSZ
17 
18 #define STATUS_BLINK_INTERVAL 500 // milliseconds
19 
20 
21 extern COLORREF nhcolor_to_RGB(int c); /* from mhmap */
22 
23 typedef struct back_buffer {
24     HWND hWnd;
25     HDC hdc;
26     HBITMAP bm;
27     HBITMAP orig_bm;
28     int width;
29     int height;
30 } back_buffer_t;
31 
back_buffer_free(back_buffer_t * back_buffer)32 void back_buffer_free(back_buffer_t * back_buffer)
33 {
34     if (back_buffer->bm != NULL) {
35         SelectObject(back_buffer->hdc, back_buffer->orig_bm);
36         DeleteObject(back_buffer->bm);
37         back_buffer->bm = NULL;
38     }
39 }
40 
back_buffer_allocate(back_buffer_t * back_buffer,int width,int height)41 void back_buffer_allocate(back_buffer_t * back_buffer, int width, int height)
42 {
43     HDC hdc = GetDC(back_buffer->hWnd);
44     back_buffer->bm = CreateCompatibleBitmap(hdc, width, height);
45     back_buffer->orig_bm =  SelectObject(back_buffer->hdc, back_buffer->bm);
46     back_buffer->width = width;
47     back_buffer->height = height;
48     ReleaseDC(back_buffer->hWnd, hdc);
49 }
50 
back_buffer_size(back_buffer_t * back_buffer,int width,int height)51 void back_buffer_size(back_buffer_t * back_buffer, int width, int height)
52 {
53     if (back_buffer->bm == NULL || back_buffer->width != width
54                                 || back_buffer->height != height) {
55         back_buffer_free(back_buffer);
56         back_buffer_allocate(back_buffer, width, height);
57     }
58 }
59 
back_buffer_init(back_buffer_t * back_buffer,HWND hWnd,int width,int height)60 void back_buffer_init(back_buffer_t * back_buffer, HWND hWnd, int width, int height)
61 {
62     back_buffer->hWnd = hWnd;
63     back_buffer->hdc = CreateCompatibleDC(NULL);
64     back_buffer->bm = NULL;
65 
66     back_buffer_size(back_buffer, width, height);
67 }
68 
69 
70 typedef struct mswin_nethack_status_window {
71     int index;
72     char window_text[NHSW_LINES][MAXWINDOWTEXT + 1];
73     mswin_status_lines * status_lines;
74     back_buffer_t back_buffer;
75     boolean blink_state; /* true = invert blink text */
76     boolean has_blink_fields; /* true if one or more has blink attriubte */
77 } NHStatusWindow, *PNHStatusWindow;
78 
79 
80 static TCHAR szStatusWindowClass[] = TEXT("MSNHStatusWndClass");
81 LRESULT CALLBACK StatusWndProc(HWND, UINT, WPARAM, LPARAM);
82 static void register_status_window_class(void);
83 static LRESULT onWMPaint(HWND hWnd, WPARAM wParam, LPARAM lParam);
84 
85 #define DEFAULT_COLOR_BG_STATUS COLOR_WINDOW
86 #define DEFAULT_COLOR_FG_STATUS COLOR_WINDOWTEXT
87 
88 HWND
mswin_init_status_window()89 mswin_init_status_window()
90 {
91     static int run_once = 0;
92     HWND ret;
93     NHStatusWindow *data;
94     RECT rt;
95     int width, height;
96 
97     if (!run_once) {
98         register_status_window_class();
99         run_once = 1;
100     }
101 
102     /* get window position */
103     if (GetNHApp()->bAutoLayout) {
104         SetRect(&rt, 0, 0, 0, 0);
105     } else {
106         mswin_get_window_placement(NHW_STATUS, &rt);
107     }
108 
109     /* create status window object */
110     width = rt.right - rt.left;
111     height = rt.bottom - rt.top;
112     ret = CreateWindow(szStatusWindowClass, NULL,
113                        WS_CHILD | WS_CLIPSIBLINGS | WS_SIZEBOX,
114                        rt.left, /* horizontal position of window */
115                        rt.top,  /* vertical position of window */
116                        width,   /* window width */
117                        height,  /* window height */
118                        GetNHApp()->hMainWnd, NULL, GetNHApp()->hApp, NULL);
119     if (!ret)
120         panic("Cannot create status window");
121 
122     /* Set window caption */
123     SetWindowText(ret, "Status");
124 
125     /* create window data */
126     data = (PNHStatusWindow) malloc(sizeof(NHStatusWindow));
127     if (!data)
128         panic("out of memory");
129 
130     ZeroMemory(data, sizeof(NHStatusWindow));
131     SetWindowLongPtr(ret, GWLP_USERDATA, (LONG_PTR) data);
132 
133     back_buffer_init(&data->back_buffer, ret, width, height);
134 
135     mswin_apply_window_style(ret);
136 
137     if (status_bg_brush == NULL) {
138         status_bg_color = (COLORREF)GetSysColor(DEFAULT_COLOR_BG_STATUS);
139         status_bg_brush = CreateSolidBrush(status_bg_color);
140     }
141 
142     if (status_fg_brush == NULL) {
143         status_fg_color = (COLORREF)GetSysColor(DEFAULT_COLOR_FG_STATUS);
144         status_fg_brush = CreateSolidBrush(status_fg_color);
145     }
146 
147     /* set cursor blink timer */
148     SetTimer(ret, 0, STATUS_BLINK_INTERVAL, NULL);
149 
150     return ret;
151 }
152 
153 void
register_status_window_class()154 register_status_window_class()
155 {
156     WNDCLASS wcex;
157     ZeroMemory(&wcex, sizeof(wcex));
158 
159     wcex.style = CS_NOCLOSE;
160     wcex.lpfnWndProc = (WNDPROC) StatusWndProc;
161     wcex.cbClsExtra = 0;
162     wcex.cbWndExtra = 0;
163     wcex.hInstance = GetNHApp()->hApp;
164     wcex.hIcon = NULL;
165     wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
166     wcex.hbrBackground = NULL;
167     wcex.lpszMenuName = NULL;
168     wcex.lpszClassName = szStatusWindowClass;
169 
170     RegisterClass(&wcex);
171 }
172 
173 LRESULT CALLBACK
StatusWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)174 StatusWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
175 {
176     PNHStatusWindow data;
177 
178     data = (PNHStatusWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
179     switch (message) {
180     case WM_MSNH_COMMAND: {
181         switch (wParam) {
182         case MSNH_MSG_PUTSTR: {
183             PMSNHMsgPutstr msg_data = (PMSNHMsgPutstr) lParam;
184             strncpy(data->window_text[data->index], msg_data->text,
185                     MAXWINDOWTEXT);
186             data->index = (data->index + 1) % NHSW_LINES;
187             InvalidateRect(hWnd, NULL, TRUE);
188         } break;
189 
190         case MSNH_MSG_CLEAR_WINDOW: {
191             data->index = 0;
192             ZeroMemory(data->window_text, sizeof(data->window_text));
193             InvalidateRect(hWnd, NULL, TRUE);
194         } break;
195 
196         case MSNH_MSG_GETTEXT: {
197             PMSNHMsgGetText msg_data = (PMSNHMsgGetText) lParam;
198             msg_data->buffer[0] = '\0';
199             size_t space_remaining = msg_data->max_size;
200 
201             for (int line = 0; line < NHSW_LINES; line++) {
202                 mswin_status_line *status_line = data->status_lines[line].lines;
203                 for (int i = 0; i < status_line->status_strings.count; i++) {
204                     mswin_status_string * status_string = status_line->status_strings.status_strings[i];
205                     if (status_string->space_in_front) {
206                         strncat(msg_data->buffer, " ", space_remaining);
207                         space_remaining = msg_data->max_size - strlen(msg_data->buffer);
208                     }
209                     strncat(msg_data->buffer, status_string->str, space_remaining);
210                     space_remaining = msg_data->max_size - strlen(msg_data->buffer);
211                 }
212                 strncat(msg_data->buffer, "\r\n", space_remaining);
213                 space_remaining = msg_data->max_size - strlen(msg_data->buffer);
214             }
215         } break;
216 
217         case MSNH_MSG_UPDATE_STATUS: {
218             PMSNHMsgUpdateStatus msg_data = (PMSNHMsgUpdateStatus) lParam;
219             data->status_lines = msg_data->status_lines;
220             InvalidateRect(hWnd, NULL, TRUE);
221         } break;
222 
223 		case MSNH_MSG_RANDOM_INPUT:
224 			nhassert(0); // unexpected
225 			break;
226 
227         } /* end switch( wParam ) { */
228     } break;
229 
230     case WM_PAINT:
231         return onWMPaint(hWnd, wParam, lParam);
232 
233     case WM_SIZE: {
234         RECT rt;
235         GetWindowRect(hWnd, &rt);
236         ScreenToClient(GetNHApp()->hMainWnd, (LPPOINT) &rt);
237         ScreenToClient(GetNHApp()->hMainWnd, ((LPPOINT) &rt) + 1);
238         mswin_update_window_placement(NHW_STATUS, &rt);
239     }
240         return FALSE;
241 
242     case WM_MOVE: {
243         RECT rt;
244         GetWindowRect(hWnd, &rt);
245         ScreenToClient(GetNHApp()->hMainWnd, (LPPOINT) &rt);
246         ScreenToClient(GetNHApp()->hMainWnd, ((LPPOINT) &rt) + 1);
247         mswin_update_window_placement(NHW_STATUS, &rt);
248     }
249         return FALSE;
250 
251     case WM_DESTROY:
252         free(data);
253         SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) 0);
254         break;
255 
256     case WM_SETFOCUS:
257         SetFocus(GetNHApp()->hMainWnd);
258         break;
259 
260     case WM_TIMER:
261         data->blink_state = !data->blink_state;
262         if (data->has_blink_fields)
263             InvalidateRect(hWnd, NULL, TRUE);
264         break;
265 
266     default:
267         return DefWindowProc(hWnd, message, wParam, lParam);
268     }
269     return 0;
270 }
271 
272 static LRESULT
onWMPaint(HWND hWnd,WPARAM wParam,LPARAM lParam)273 onWMPaint(HWND hWnd, WPARAM wParam, LPARAM lParam)
274 {
275     SIZE sz;
276     WCHAR wbuf[BUFSZ];
277     RECT rt;
278     PAINTSTRUCT ps;
279     PNHStatusWindow data;
280     int width, height;
281     RECT clear_rect;
282     HDC front_buffer_hdc;
283 
284     data = (PNHStatusWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
285 
286     front_buffer_hdc = BeginPaint(hWnd, &ps);
287     GetClientRect(hWnd, &rt);
288 
289     width = rt.right - rt.left;
290     height = rt.bottom - rt.top;
291 
292     back_buffer_size(&data->back_buffer, width, height);
293 
294     HDC hdc = data->back_buffer.hdc;
295 
296     SetBkColor(hdc, status_bg_color);
297     SetTextColor(hdc, status_fg_color);
298 
299     clear_rect.left = 0;
300     clear_rect.top = 0;
301     clear_rect.right = width;
302     clear_rect.bottom = height;
303 
304     FillRect(hdc, &clear_rect, status_bg_brush);
305 
306     if (data->status_lines != NULL) {
307 
308         data->has_blink_fields = FALSE;
309 
310         for (int line = 0; line < NHSW_LINES; line++) {
311             LONG left = rt.left;
312             LONG cy = 0;
313             int vlen;
314 
315             mswin_status_line * status_line = &data->status_lines->lines[line];
316 
317             for (int i = 0; i < status_line->status_strings.count; i++) {
318                 mswin_status_string * status_string = status_line->status_strings.status_strings[i];
319                 int clr, atr;
320                 int fntatr = ATR_NONE;
321                 COLORREF nFg, nBg;
322 
323                 if (status_string->str == NULL || status_string->str[0] == '\0')
324                     continue;
325 
326                 clr = status_string->color;
327                 atr = status_string->attribute;
328 
329                 const char *str = status_string->str;
330 
331                 vlen = strlen(str);
332 
333                 if (atr & HL_BOLD)
334                     fntatr = ATR_BOLD;
335                 else if (atr & HL_INVERSE)
336                     fntatr = ATR_INVERSE;
337                 else if (atr & HL_ULINE)
338                     fntatr = ATR_ULINE;
339                 else if (atr & HL_BLINK) {
340                     data->has_blink_fields = TRUE;
341                     if (data->blink_state) {
342                         fntatr = ATR_INVERSE;
343                         atr |= HL_INVERSE;
344                     }
345                 }
346                 else if (atr & HL_DIM)
347                     fntatr = ATR_DIM;
348 
349                 cached_font * fnt = mswin_get_font(NHW_STATUS, fntatr, hdc, FALSE);
350 
351                 BOOL useUnicode = fnt->supportsUnicode;
352 
353                 winos_ascii_to_wide_str(str, wbuf, SIZE(wbuf));
354 
355                 nFg = (clr == NO_COLOR ? status_fg_color
356                     : ((clr >= 0 && clr < CLR_MAX) ? nhcolor_to_RGB(clr)
357                         : status_fg_color));
358 
359                 if (atr & HL_DIM) {
360                     /* make a dim representation - this can produce color shift */
361                     float redReduction = 0.5f;
362                     float greenReduction = 0.5f;
363                     float blueReduction = 0.25f;
364                     uchar red = (uchar)(GetRValue(nFg) * (1.0f - redReduction));
365                     uchar green = (uchar)(GetGValue(nFg) * (1.0f - greenReduction));
366                     uchar blue = (uchar)(GetBValue(nFg) * (1.0f - blueReduction));
367                     nFg = RGB(red, green, blue);
368                 }
369 
370                 nBg = status_bg_color;
371 
372                 if (status_string->space_in_front)
373                     rt.left += fnt->width;
374 
375                 sz.cy = -1;
376 
377                 if (status_string->draw_bar && iflags.wc2_hitpointbar) {
378                     /* NOTE: we current don't support bar attributes */
379 
380                     /* when we are drawing bar we need to look at the hp status
381                      * field to get the correct percentage and color */
382                     COLORREF bar_color = nhcolor_to_RGB(status_string->bar_color);
383                     HBRUSH back_brush = CreateSolidBrush(bar_color);
384                     RECT barrect;
385 
386                     /* prepare for drawing */
387                     SelectObject(hdc, fnt->hFont);
388                     SetBkMode(hdc, OPAQUE);
389                     SetBkColor(hdc, status_bg_color);
390                     SetTextColor(hdc, status_fg_color);
391 
392                     if (useUnicode) {
393                         /* get bounding rectangle */
394                         GetTextExtentPoint32W(hdc, wbuf, vlen, &sz);
395 
396                         /* first draw title normally */
397                         DrawTextW(hdc, wbuf, vlen, &rt, DT_LEFT);
398                     }
399                     else {
400                         /* get bounding rectangle */
401                         GetTextExtentPoint32A(hdc, str, vlen, &sz);
402 
403                         /* first draw title normally */
404                         DrawTextA(hdc, str, vlen, &rt, DT_LEFT);
405                     }
406                     int bar_percent = status_string->bar_percent;
407                     if (bar_percent > 0) {
408                         /* calc bar length */
409                         barrect.left = rt.left;
410                         barrect.top = rt.top;
411                         barrect.bottom = sz.cy;
412                         if (bar_percent > 0)
413                             barrect.right = (int)((bar_percent * sz.cx) / 100);
414                         /* else
415                             barrect.right = sz.cx; */
416 
417                             /* then draw hpbar on top of title */
418                         FillRect(hdc, &barrect, back_brush);
419                         SetBkMode(hdc, TRANSPARENT);
420                         SetTextColor(hdc, nBg);
421 
422                         if (useUnicode)
423                             DrawTextW(hdc, wbuf, vlen, &barrect, DT_LEFT);
424                         else
425                             DrawTextA(hdc, str, vlen, &barrect, DT_LEFT);
426                     }
427                     DeleteObject(back_brush);
428                 }
429                 else {
430                     if (atr & HL_INVERSE) {
431                         COLORREF tmp = nFg;
432                         nFg = nBg;
433                         nBg = tmp;
434                     }
435 
436                     /* prepare for drawing */
437                     SelectObject(hdc, fnt->hFont);
438                     SetBkMode(hdc, OPAQUE);
439                     SetBkColor(hdc, nBg);
440                     SetTextColor(hdc, nFg);
441 
442                     if (useUnicode) {
443                         /* get bounding rectangle */
444                         GetTextExtentPoint32W(hdc, wbuf, vlen, &sz);
445 
446                         /* draw */
447                         DrawTextW(hdc, wbuf, vlen, &rt, DT_LEFT);
448                     }
449                     else {
450                         /* get bounding rectangle */
451                         GetTextExtentPoint32A(hdc, str, vlen, &sz);
452 
453                         /* draw */
454                         DrawTextA(hdc, str, vlen, &rt, DT_LEFT);
455                     }
456                 }
457                 assert(sz.cy >= 0);
458 
459                 rt.left += sz.cx;
460                 cy = max(cy, sz.cy);
461             }
462 
463             rt.left = left;
464             rt.top += cy;
465         }
466     }
467 
468     BitBlt(front_buffer_hdc, 0, 0, width, height, hdc, 0, 0, SRCCOPY);
469 
470     EndPaint(hWnd, &ps);
471 
472     return 0;
473 }
474 
475 void
mswin_status_window_size(HWND hWnd,LPSIZE sz)476 mswin_status_window_size(HWND hWnd, LPSIZE sz)
477 {
478     RECT rt;
479 
480     GetClientRect(hWnd, &rt);
481 
482     PNHStatusWindow data = (PNHStatusWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
483     if (data) {
484         HDC hdc = GetDC(hWnd);
485         cached_font * font = mswin_get_font(NHW_STATUS, ATR_NONE, hdc, FALSE);
486         HGDIOBJ saveFont = SelectObject(hdc, font->hFont);
487 
488         SIZE text_sz;
489         GetTextExtentPoint32(hdc, _T("W"), 1, &text_sz);
490 
491         TEXTMETRIC tm;
492         GetTextMetrics(hdc, &tm);
493 
494         rt.bottom = rt.top + text_sz.cy * NHSW_LINES;
495 
496         SelectObject(hdc, saveFont);
497         ReleaseDC(hWnd, hdc);
498     }
499     AdjustWindowRect(&rt, GetWindowLong(hWnd, GWL_STYLE), FALSE);
500     sz->cx = rt.right - rt.left;
501     sz->cy = rt.bottom - rt.top;
502 }
503