1 /* NetHack 3.7	mhmsgwnd.c	$NHDT-Date: 1596498357 2020/08/03 23:45:57 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.40 $ */
2 /* Copyright (C) 2001 by Alex Kompel 	 */
3 /* NetHack may be freely redistributed.  See license for details. */
4 
5 #include "winMS.h"
6 #include "mhmsgwnd.h"
7 #include "mhmsg.h"
8 #include "mhfont.h"
9 
10 #define MSG_WRAP_TEXT
11 
12 #define MSG_VISIBLE_LINES max(iflags.wc_vary_msgcount, 1)
13 #define MAX_MSG_LINES 128
14 #define MSG_LINES (int) min(iflags.msg_history, MAX_MSG_LINES)
15 #define MAXWINDOWTEXT TBUFSZ
16 
17 #define DEFAULT_COLOR_BG_MSG COLOR_WINDOW
18 #define DEFAULT_COLOR_FG_MSG COLOR_WINDOWTEXT
19 
20 #define MORE "--More--"
21 
22 struct window_line {
23     int attr;
24     char text[MAXWINDOWTEXT + 1];
25 };
26 
27 typedef struct mswin_nethack_message_window {
28     size_t max_text;
29     struct window_line window_text[MAX_MSG_LINES];
30     int lines_last_turn; /* lines added during the last turn */
31     int lines_not_seen;  /* lines not yet seen by user after last turn or
32                             --More-- */
33     int nevermore;       /* We want no more --More-- prompts */
34 
35     int xChar;  /* horizontal scrolling unit */
36     int yChar;  /* vertical scrolling unit */
37     int xUpper; /* average width of uppercase letters */
38     int xPos;   /* current horizontal scrolling position */
39     int yPos;   /* current vertical scrolling position */
40     int xMax;   /* maximum horizontal scrolling position */
41     int yMax;   /* maximum vertical scrolling position */
42     int xPage;  /* page size of horizontal scroll bar */
43 } NHMessageWindow, *PNHMessageWindow;
44 #define LINE_PADDING_LEFT(data)  (data->xChar * (2 - data->xPos))
45 #define LINE_PADDING_RIGHT(data)  (0)
46 
47 static TCHAR szMessageWindowClass[] = TEXT("MSNHMessageWndClass");
48 LRESULT CALLBACK NHMessageWndProc(HWND, UINT, WPARAM, LPARAM);
49 static void register_message_window_class(void);
50 static void onMSNHCommand(HWND hWnd, WPARAM wParam, LPARAM lParam);
51 static void onMSNH_VScroll(HWND hWnd, WPARAM wParam, LPARAM lParam);
52 #ifndef MSG_WRAP_TEXT
53 static void onMSNH_HScroll(HWND hWnd, WPARAM wParam, LPARAM lParam);
54 #endif
55 static COLORREF setMsgTextColor(HDC hdc, int gray);
56 static void onPaint(HWND hWnd);
57 static void onCreate(HWND hWnd, WPARAM wParam, LPARAM lParam);
58 static BOOL can_append_text(HWND hWnd, int attr, const char *text);
59 /* check if text can be appended to the last line without wrapping */
60 
61 static BOOL more_prompt_check(HWND hWnd);
62 /* check if "--more--" prompt needs to be displayed */
63 
64 #ifdef USER_SOUNDS
65 extern void play_sound_for_message(const char *str);
66 #endif
67 
68 HWND
mswin_init_message_window(void)69 mswin_init_message_window(void)
70 {
71     static int run_once = 0;
72     HWND ret;
73     DWORD style;
74     RECT rt;
75 
76     if (!run_once) {
77         register_message_window_class();
78         run_once = 1;
79     }
80 
81     /* get window position */
82     if (GetNHApp()->bAutoLayout) {
83         SetRect(&rt, 0, 0, 0, 0);
84     } else {
85         mswin_get_window_placement(NHW_MESSAGE, &rt);
86     }
87 
88 #ifdef MSG_WRAP_TEXT
89     style = WS_CHILD | WS_CLIPSIBLINGS | WS_VSCROLL | WS_SIZEBOX;
90 #else
91     style = WS_CHILD | WS_CLIPSIBLINGS | WS_VSCROLL | WS_HSCROLL | WS_SIZEBOX;
92 #endif
93 
94     ret = CreateWindowEx(
95         WS_EX_CLIENTEDGE, szMessageWindowClass, /* registered class name */
96         NULL,                                   /* window name */
97         style,                                  /* window style */
98         rt.left,              /* horizontal position of window */
99         rt.top,               /* vertical position of window */
100         rt.right - rt.left,   /* window width */
101         rt.bottom - rt.top,   /* window height */
102         GetNHApp()->hMainWnd, /* handle to parent or owner window */
103         NULL,                 /* menu handle or child identifier */
104         GetNHApp()->hApp,     /* handle to application instance */
105         NULL);                /* window-creation data */
106 
107     if (!ret)
108         panic("Cannot create message window");
109 
110     /* Set window caption */
111     SetWindowText(ret, "Messages");
112 
113     mswin_apply_window_style(ret);
114 
115     return ret;
116 }
117 
118 void
register_message_window_class(void)119 register_message_window_class(void)
120 {
121     WNDCLASS wcex;
122     ZeroMemory(&wcex, sizeof(wcex));
123 
124     wcex.style = CS_NOCLOSE;
125     wcex.lpfnWndProc = (WNDPROC) NHMessageWndProc;
126     wcex.cbClsExtra = 0;
127     wcex.cbWndExtra = 0;
128     wcex.hInstance = GetNHApp()->hApp;
129     wcex.hIcon = NULL;
130     wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
131     wcex.hbrBackground = message_bg_brush
132                              ? message_bg_brush
133                              : SYSCLR_TO_BRUSH(DEFAULT_COLOR_BG_MSG);
134     wcex.lpszMenuName = NULL;
135     wcex.lpszClassName = szMessageWindowClass;
136 
137     RegisterClass(&wcex);
138 }
139 
140 LRESULT CALLBACK
NHMessageWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)141 NHMessageWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
142 {
143     switch (message) {
144     case WM_CREATE:
145         onCreate(hWnd, wParam, lParam);
146         break;
147 
148     case WM_MSNH_COMMAND:
149         onMSNHCommand(hWnd, wParam, lParam);
150         break;
151 
152     case WM_PAINT:
153         onPaint(hWnd);
154         break;
155 
156     case WM_SETFOCUS:
157         SetFocus(GetNHApp()->hMainWnd);
158         break;
159 
160 #ifndef MSG_WRAP_TEXT
161     case WM_HSCROLL:
162         onMSNH_HScroll(hWnd, wParam, lParam);
163         break;
164 #endif
165 
166     case WM_VSCROLL:
167         onMSNH_VScroll(hWnd, wParam, lParam);
168         break;
169 
170     case WM_DESTROY: {
171         PNHMessageWindow data;
172         data = (PNHMessageWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
173         free(data);
174         SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) 0);
175     } break;
176 
177     case WM_SIZE: {
178         SCROLLINFO si;
179         int xNewSize;
180         int yNewSize;
181         PNHMessageWindow data;
182         RECT rt;
183 
184         data = (PNHMessageWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
185 
186         xNewSize = LOWORD(lParam);
187         yNewSize = HIWORD(lParam);
188 
189         if (xNewSize > 0 || yNewSize > 0) {
190 #ifndef MSG_WRAP_TEXT
191             data->xPage = xNewSize / data->xChar;
192             data->xMax = max(0, (int) (1 + data->max_text - data->xPage));
193             data->xPos = min(data->xPos, data->xMax);
194 
195             ZeroMemory(&si, sizeof(si));
196             si.cbSize = sizeof(si);
197             si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
198             si.nMin = 0;
199             si.nMax = data->max_text;
200             si.nPage = data->xPage;
201             si.nPos = data->xPos;
202             SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);
203 #endif
204 
205             data->yMax = MSG_LINES - 1;
206             data->yPos = min(data->yPos, data->yMax);
207 
208             ZeroMemory(&si, sizeof(si));
209             si.cbSize = sizeof(si);
210             si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
211             si.nMin = MSG_VISIBLE_LINES;
212             si.nMax = data->yMax + MSG_VISIBLE_LINES - 1;
213             si.nPage = MSG_VISIBLE_LINES;
214             si.nPos = data->yPos;
215             SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
216         }
217 
218         /* update NetHack internal window position */
219         GetWindowRect(hWnd, &rt);
220         ScreenToClient(GetNHApp()->hMainWnd, (LPPOINT) &rt);
221         ScreenToClient(GetNHApp()->hMainWnd, ((LPPOINT) &rt) + 1);
222         mswin_update_window_placement(NHW_MESSAGE, &rt);
223 
224         /* redraw window - it does not handle incremental resizing too well */
225         InvalidateRect(hWnd, NULL, TRUE);
226     } break;
227 
228     case WM_MOVE: {
229         RECT rt;
230         GetWindowRect(hWnd, &rt);
231         ScreenToClient(GetNHApp()->hMainWnd, (LPPOINT) &rt);
232         ScreenToClient(GetNHApp()->hMainWnd, ((LPPOINT) &rt) + 1);
233         mswin_update_window_placement(NHW_MESSAGE, &rt);
234     } break;
235 
236     default:
237         return DefWindowProc(hWnd, message, wParam, lParam);
238     }
239     return 0;
240 }
241 
242 void
onMSNHCommand(HWND hWnd,WPARAM wParam,LPARAM lParam)243 onMSNHCommand(HWND hWnd, WPARAM wParam, LPARAM lParam)
244 {
245     PNHMessageWindow data;
246 
247     data = (PNHMessageWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
248     switch (wParam) {
249     case MSNH_MSG_PUTSTR: {
250         /* Add the passed in message to the existing text.  Support the
251          * adding of text that ends in newline.  A newline in text
252          * will force any subsequent text that is added to be added on
253          * a new output line.
254          *
255          * TODO: Text can be added with newlines occurring within the text not
256          *       just at the end.  As currently implemented, this can cause
257          *       the text to be rendered such that the text following the
258          *       newline is rendered on a new line.  This can cause a poor
259          *       user experience when the user has set only a single text line
260          *       for the message window.  In this case, the user will not see
261          *       any line other then the last line of text and the --MORE--
262          *       message thus missing any text that appears before the last
263          *       embedded newline.  This does not meet the requirements of the
264          *       message window.
265          *       This code should be changed to do the right thing and split
266          *       the text so that only lines that end in newlines are added to
267          *       the stored window text.
268          */
269         PMSNHMsgPutstr msg_data = (PMSNHMsgPutstr) lParam;
270         SCROLLINFO si;
271         char *p;
272 
273         if (msg_data->append == 1) {
274             /* Forcibly append to line, even if we pass the edge */
275             strncat(data->window_text[MSG_LINES - 1].text, msg_data->text,
276                     MAXWINDOWTEXT
277                         - strlen(data->window_text[MSG_LINES - 1].text));
278         } else if (msg_data->append < 0) {
279             /* remove that many chars */
280             int len = strlen(data->window_text[MSG_LINES - 1].text);
281             int newend = max(len + msg_data->append, 0);
282             data->window_text[MSG_LINES - 1].text[newend] = '\0';
283         } else {
284             if (can_append_text(hWnd, msg_data->attr, msg_data->text)) {
285                 strncat(data->window_text[MSG_LINES - 1].text, "  ",
286                         MAXWINDOWTEXT
287                             - strlen(data->window_text[MSG_LINES - 1].text));
288                 strncat(data->window_text[MSG_LINES - 1].text, msg_data->text,
289                         MAXWINDOWTEXT
290                             - strlen(data->window_text[MSG_LINES - 1].text));
291             } else {
292                 /* check for "--more--" */
293                 if (!data->nevermore && more_prompt_check(hWnd)) {
294                     int okkey = 0;
295                     char tmptext[MAXWINDOWTEXT + 1];
296 
297                     // @@@ Ok respnses
298 
299                     /* save original text */
300                     strcpy(tmptext, data->window_text[MSG_LINES - 1].text);
301 
302                     /* text could end in newline so strip it */
303                     strip_newline(data->window_text[MSG_LINES - 1].text);
304 
305                     /* append more prompt and indicate the update */
306                     strncat(
307                         data->window_text[MSG_LINES - 1].text, MORE,
308                         MAXWINDOWTEXT
309                             - strlen(data->window_text[MSG_LINES - 1].text));
310                     InvalidateRect(hWnd, NULL, TRUE);
311 
312                     /* get the input */
313                     while (!okkey) {
314                         int c = mswin_nhgetch();
315 
316                         switch (c) {
317                         /* space or enter */
318                         case ' ':
319                         case '\015':
320                             okkey = 1;
321                             break;
322                         /* ESC */
323                         case '\033':
324                             data->nevermore = 1;
325                             okkey = 1;
326                             break;
327                         default:
328                             break;
329                         }
330                     }
331 
332                     /* restore original text */
333                     strcpy(data->window_text[MSG_LINES - 1].text, tmptext);
334 
335                     data->lines_not_seen = 0;
336                 }
337 
338                 /* check if the string is empty */
339                 for (p = data->window_text[MSG_LINES - 1].text;
340                      *p && isspace((uchar) *p); p++)
341                     ;
342 
343                 if (*p) {
344                     /* last string is not empty - scroll up */
345                     memmove(&data->window_text[0], &data->window_text[1],
346                             (MSG_LINES - 1) * sizeof(data->window_text[0]));
347                 }
348 
349                 /* append new text to the end of the array */
350                 data->window_text[MSG_LINES - 1].attr = msg_data->attr;
351                 strncpy(data->window_text[MSG_LINES - 1].text, msg_data->text,
352                         MAXWINDOWTEXT);
353 
354                 data->lines_not_seen++;
355                 data->lines_last_turn++;
356             }
357         }
358 
359         /* reset V-scroll position to display new text */
360         data->yPos = data->yMax;
361 
362         ZeroMemory(&si, sizeof(si));
363         si.cbSize = sizeof(si);
364         si.fMask = SIF_POS;
365         si.nPos = data->yPos;
366         SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
367 
368         /* update window content */
369         InvalidateRect(hWnd, NULL, TRUE);
370 
371 #ifdef USER_SOUNDS
372         if (!GetNHApp()->bNoSounds)
373             play_sound_for_message(msg_data->text);
374 #endif
375     } break;
376 
377     case MSNH_MSG_CLEAR_WINDOW: {
378         data->lines_last_turn = 0;
379         data->lines_not_seen = 0;
380         data->nevermore = 0;
381         break;
382     }
383     case MSNH_MSG_CARET:
384         /* Create or destroy a caret */
385         if (*(int *) lParam)
386             CreateCaret(hWnd, NULL, 0, data->yChar);
387         else {
388             DestroyCaret();
389             /* this means we just did something interactive in this window, so
390                we
391                don't need a --More-- for the lines above.
392                */
393             data->lines_not_seen = 0;
394         }
395         break;
396 
397     case MSNH_MSG_GETTEXT: {
398         PMSNHMsgGetText msg_data = (PMSNHMsgGetText) lParam;
399         int i;
400         size_t buflen;
401 
402         buflen = 0;
403         for (i = 0; i < MSG_LINES; i++)
404             if (*data->window_text[i].text) {
405                 strncpy(&msg_data->buffer[buflen], data->window_text[i].text,
406                         msg_data->max_size - buflen);
407                 buflen += strlen(data->window_text[i].text);
408                 if (buflen >= msg_data->max_size)
409                     break;
410 
411                 strncpy(&msg_data->buffer[buflen], "\r\n",
412                         msg_data->max_size - buflen);
413                 buflen += 2;
414                 if (buflen > msg_data->max_size)
415                     break;
416             }
417     } break;
418 
419 	case MSNH_MSG_RANDOM_INPUT:
420 		nhassert(0); // unexpected
421 		break;
422 
423     } /* switch( wParam ) */
424 }
425 
426 void
onMSNH_VScroll(HWND hWnd,WPARAM wParam,LPARAM lParam)427 onMSNH_VScroll(HWND hWnd, WPARAM wParam, LPARAM lParam)
428 {
429     PNHMessageWindow data;
430     SCROLLINFO si;
431     int yInc;
432 
433     UNREFERENCED_PARAMETER(lParam);
434 
435     /* get window data */
436     data = (PNHMessageWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
437 
438     ZeroMemory(&si, sizeof(si));
439     si.cbSize = sizeof(si);
440     si.fMask = SIF_PAGE | SIF_POS;
441     GetScrollInfo(hWnd, SB_VERT, &si);
442 
443     switch (LOWORD(wParam)) {
444     // User clicked the shaft above the scroll box.
445 
446     case SB_PAGEUP:
447         yInc = -(int) si.nPage;
448         break;
449 
450     // User clicked the shaft below the scroll box.
451 
452     case SB_PAGEDOWN:
453         yInc = si.nPage;
454         break;
455 
456     // User clicked the top arrow.
457 
458     case SB_LINEUP:
459         yInc = -1;
460         break;
461 
462     // User clicked the bottom arrow.
463 
464     case SB_LINEDOWN:
465         yInc = 1;
466         break;
467 
468     // User dragged the scroll box.
469 
470     case SB_THUMBTRACK:
471         yInc = HIWORD(wParam) - data->yPos;
472         break;
473 
474     default:
475         yInc = 0;
476     }
477 
478     // If applying the vertical scrolling increment does not
479     // take the scrolling position out of the scrolling range,
480     // increment the scrolling position, adjust the position
481     // of the scroll box, and update the window. UpdateWindow
482     // sends the WM_PAINT message.
483 
484     if (yInc = max(MSG_VISIBLE_LINES - data->yPos,
485                    min(yInc, data->yMax - data->yPos))) {
486         data->yPos += yInc;
487         /* ScrollWindowEx(hWnd, 0, -data->yChar * yInc,
488                 (CONST RECT *) NULL, (CONST RECT *) NULL,
489                 (HRGN) NULL, (LPRECT) NULL, SW_INVALIDATE | SW_ERASE);
490         */
491         InvalidateRect(hWnd, NULL, TRUE);
492 
493         ZeroMemory(&si, sizeof(si));
494         si.cbSize = sizeof(si);
495         si.fMask = SIF_POS;
496         si.nPos = data->yPos;
497         SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
498 
499         UpdateWindow(hWnd);
500     }
501 }
502 
503 #ifndef MSG_WRAP_TEXT
504 void
onMSNH_HScroll(HWND hWnd,WPARAM wParam,LPARAM lParam)505 onMSNH_HScroll(HWND hWnd, WPARAM wParam, LPARAM lParam)
506 {
507     PNHMessageWindow data;
508     SCROLLINFO si;
509     int xInc;
510 
511     /* get window data */
512     data = (PNHMessageWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
513 
514     ZeroMemory(&si, sizeof(si));
515     si.cbSize = sizeof(si);
516     si.fMask = SIF_PAGE;
517     GetScrollInfo(hWnd, SB_HORZ, &si);
518 
519     switch (LOWORD(wParam)) {
520     // User clicked shaft left of the scroll box.
521 
522     case SB_PAGEUP:
523         xInc = -(int) si.nPage;
524         break;
525 
526     // User clicked shaft right of the scroll box.
527 
528     case SB_PAGEDOWN:
529         xInc = si.nPage;
530         break;
531 
532     // User clicked the left arrow.
533 
534     case SB_LINEUP:
535         xInc = -1;
536         break;
537 
538     // User clicked the right arrow.
539 
540     case SB_LINEDOWN:
541         xInc = 1;
542         break;
543 
544     // User dragged the scroll box.
545 
546     case SB_THUMBTRACK:
547         xInc = HIWORD(wParam) - data->xPos;
548         break;
549 
550     default:
551         xInc = 0;
552     }
553 
554     // If applying the horizontal scrolling increment does not
555     // take the scrolling position out of the scrolling range,
556     // increment the scrolling position, adjust the position
557     // of the scroll box, and update the window.
558 
559     if (xInc = max(-data->xPos, min(xInc, data->xMax - data->xPos))) {
560         data->xPos += xInc;
561         ScrollWindowEx(hWnd, -data->xChar * xInc, 0, (CONST RECT *) NULL,
562                        (CONST RECT *) NULL, (HRGN) NULL, (LPRECT) NULL,
563                        SW_INVALIDATE | SW_ERASE);
564 
565         ZeroMemory(&si, sizeof(si));
566         si.cbSize = sizeof(si);
567         si.fMask = SIF_POS;
568         si.nPos = data->xPos;
569         SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);
570         UpdateWindow(hWnd);
571     }
572 }
573 #endif // MSG_WRAP_TEXT
574 
575 COLORREF
setMsgTextColor(HDC hdc,int gray)576 setMsgTextColor(HDC hdc, int gray)
577 {
578     COLORREF fg, color1, color2;
579     if (gray) {
580         if (message_bg_brush) {
581             color1 = message_bg_color;
582             color2 = message_fg_color;
583         } else {
584             color1 = (COLORREF) GetSysColor(DEFAULT_COLOR_BG_MSG);
585             color2 = (COLORREF) GetSysColor(DEFAULT_COLOR_FG_MSG);
586         }
587         /* Make a "gray" color by taking the average of the individual R,G,B
588            components of two colors. Thanks to Jonathan del Strother */
589         fg = RGB((GetRValue(color1) + GetRValue(color2)) / 2,
590                  (GetGValue(color1) + GetGValue(color2)) / 2,
591                  (GetBValue(color1) + GetBValue(color2)) / 2);
592     } else {
593         fg = message_fg_brush ? message_fg_color
594                               : (COLORREF) GetSysColor(DEFAULT_COLOR_FG_MSG);
595     }
596 
597     return SetTextColor(hdc, fg);
598 }
599 
600 void
onPaint(HWND hWnd)601 onPaint(HWND hWnd)
602 {
603     PAINTSTRUCT ps;
604     HDC hdc;
605     PNHMessageWindow data;
606     RECT client_rt, draw_rt;
607     int FirstLine, LastLine;
608     int i, y;
609     HGDIOBJ oldFont;
610     TCHAR wbuf[MAXWINDOWTEXT + 2];
611     size_t wlen;
612     COLORREF OldBg, OldFg;
613 
614     hdc = BeginPaint(hWnd, &ps);
615 
616     OldBg = SetBkColor(
617         hdc, message_bg_brush ? message_bg_color
618                               : (COLORREF) GetSysColor(DEFAULT_COLOR_BG_MSG));
619     OldFg = setMsgTextColor(hdc, 0);
620 
621     data = (PNHMessageWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
622 
623     GetClientRect(hWnd, &client_rt);
624 
625     if (!IsRectEmpty(&ps.rcPaint)) {
626         FirstLine = max(
627             0, data->yPos - (client_rt.bottom - ps.rcPaint.top) / data->yChar
628                    + 1);
629         LastLine =
630             min(MSG_LINES - 1,
631                 data->yPos
632                     - (client_rt.bottom - ps.rcPaint.bottom) / data->yChar);
633         y = min(ps.rcPaint.bottom, client_rt.bottom);
634         for (i = LastLine; i >= FirstLine; i--) {
635             char tmptext[MAXWINDOWTEXT + 1];
636 
637             draw_rt.left = LINE_PADDING_LEFT(data);
638             draw_rt.right = client_rt.right - LINE_PADDING_RIGHT(data);
639             draw_rt.top = y - data->yChar;
640             draw_rt.bottom = y;
641 
642             cached_font * font = mswin_get_font(NHW_MESSAGE,
643                                         data->window_text[i].attr, hdc, FALSE);
644             oldFont = SelectObject(hdc, font->hFont);
645 
646             /* convert to UNICODE stripping newline */
647             strcpy(tmptext, data->window_text[i].text);
648             strip_newline(tmptext);
649             NH_A2W(tmptext, wbuf, sizeof(wbuf));
650             wlen = _tcslen(wbuf);
651             setMsgTextColor(hdc, i < (MSG_LINES - data->lines_last_turn));
652 #ifdef MSG_WRAP_TEXT
653             /* Find out how large the bounding rectangle of the text is */
654             DrawText(hdc, wbuf, wlen, &draw_rt,
655                      DT_NOPREFIX | DT_WORDBREAK | DT_CALCRECT);
656             /* move that rectangle up, so that the bottom remains at the same
657              * height */
658             draw_rt.top = y - (draw_rt.bottom - draw_rt.top);
659             draw_rt.bottom = y;
660 
661             /* Now really draw it */
662             DrawText(hdc, wbuf, wlen, &draw_rt, DT_NOPREFIX | DT_WORDBREAK);
663 
664             /* Find out the cursor (caret) position */
665             if (i == MSG_LINES - 1) {
666                 int nnum, numfit;
667                 SIZE size = {0};
668                 TCHAR *nbuf;
669                 int nlen;
670 
671                 nbuf = wbuf;
672                 nlen = wlen;
673                 while (nlen) {
674                     /* Get the number of characters that fit on the line */
675                     GetTextExtentExPoint(hdc, nbuf, nlen,
676                                          draw_rt.right - draw_rt.left,
677                                          &numfit, NULL, &size);
678                     /* Search back to a space */
679                     nnum = numfit;
680                     if (numfit < nlen) {
681                         while (nnum > 0 && nbuf[nnum] != ' ')
682                             nnum--;
683                         /* If no space found, break wherever */
684                         if (nnum == 0)
685                             nnum = numfit;
686                     }
687                     nbuf += nnum;
688                     nlen -= nnum;
689                     if (*nbuf == ' ') {
690                         nbuf++;
691                         nlen--;
692                     }
693                 }
694                 /* The last size is the size of the last line. Set the caret
695                    there.
696                    This will fail automatically if we don't own the caret
697                    (i.e.,
698                    when not in a question.)
699                  */
700                 SetCaretPos(draw_rt.left + size.cx,
701                             draw_rt.bottom - data->yChar);
702             }
703 #else
704             DrawText(hdc, wbuf, wlen, &draw_rt, DT_NOPREFIX);
705             SetCaretPos(draw_rt.left + size.cx, draw_rt.bottom - data->yChar);
706 #endif
707             SelectObject(hdc, oldFont);
708             y -= draw_rt.bottom - draw_rt.top;
709         }
710     }
711     SetTextColor(hdc, OldFg);
712     SetBkColor(hdc, OldBg);
713     EndPaint(hWnd, &ps);
714 }
715 
716 void
onCreate(HWND hWnd,WPARAM wParam,LPARAM lParam)717 onCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
718 {
719     PNHMessageWindow data;
720     SIZE dummy;
721 
722     UNREFERENCED_PARAMETER(wParam);
723     UNREFERENCED_PARAMETER(lParam);
724 
725     /* set window data */
726     data = (PNHMessageWindow) malloc(sizeof(NHMessageWindow));
727     if (!data)
728         panic("out of memory");
729     ZeroMemory(data, sizeof(NHMessageWindow));
730     data->max_text = MAXWINDOWTEXT;
731     SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) data);
732 
733     /* re-calculate window size (+ font size) */
734     mswin_message_window_size(hWnd, &dummy);
735 }
736 
737 void
mswin_message_window_size(HWND hWnd,LPSIZE sz)738 mswin_message_window_size(HWND hWnd, LPSIZE sz)
739 {
740     HDC hdc;
741     HGDIOBJ saveFont;
742     TEXTMETRIC tm;
743     PNHMessageWindow data;
744     RECT rt, client_rt;
745 
746     data = (PNHMessageWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
747     if (!data)
748         return;
749 
750     /* -- Calculate the font size -- */
751     /* Get the handle to the client area's device context. */
752     hdc = GetDC(hWnd);
753     cached_font * font = mswin_get_font(NHW_MESSAGE, ATR_NONE, hdc, FALSE);
754     saveFont = SelectObject(hdc, font->hFont);
755 
756     /* Extract font dimensions from the text metrics. */
757     GetTextMetrics(hdc, &tm);
758     data->xChar = tm.tmAveCharWidth;
759     data->xUpper = (tm.tmPitchAndFamily & 1 ? 3 : 2) * data->xChar / 2;
760     data->yChar = tm.tmHeight + tm.tmExternalLeading;
761     data->xPage = 1;
762 
763     /* Free the device context.  */
764     SelectObject(hdc, saveFont);
765     ReleaseDC(hWnd, hdc);
766 
767     /* -- calculate window size -- */
768     GetWindowRect(hWnd, &rt);
769     sz->cx = rt.right - rt.left;
770     sz->cy = rt.bottom - rt.top;
771 
772     /* set size to accommodate MSG_VISIBLE_LINES and
773        horizontal scroll bar (difference between window rect and client rect
774        */
775     GetClientRect(hWnd, &client_rt);
776     sz->cy = sz->cy - (client_rt.bottom - client_rt.top)
777              + data->yChar * MSG_VISIBLE_LINES;
778 }
779 
780 /* check if text can be appended to the last line without wrapping */
781 BOOL
can_append_text(HWND hWnd,int attr,const char * text)782 can_append_text(HWND hWnd, int attr, const char *text)
783 {
784     PNHMessageWindow data;
785     char tmptext[MAXWINDOWTEXT + 1];
786     HDC hdc;
787     HGDIOBJ saveFont;
788     RECT draw_rt;
789     BOOL retval = FALSE;
790 
791     data = (PNHMessageWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
792 
793     /* cannot append if lines_not_seen is 0 (beginning of the new turn */
794     if (data->lines_not_seen == 0)
795         return FALSE;
796 
797     /* cannot append text with different attrbutes */
798     if (data->window_text[MSG_LINES - 1].attr != attr)
799         return FALSE;
800 
801     /* cannot append if current line ends in newline */
802     if (str_end_is(data->window_text[MSG_LINES - 1].text, "\n"))
803         return FALSE;
804 
805     /* check if the maximum string langth will be exceeded */
806     if (strlen(data->window_text[MSG_LINES - 1].text) + 2
807             + /* space characters */
808             strlen(text) + strlen(MORE)
809         >= MAXWINDOWTEXT)
810         return FALSE;
811 
812     /* check if the text is going to fit into a single line */
813     strcpy(tmptext, data->window_text[MSG_LINES - 1].text);
814     strcat(tmptext, "  ");
815     strcat(tmptext, text);
816     strip_newline(tmptext);
817     strcat(tmptext, MORE);
818 
819     hdc = GetDC(hWnd);
820     cached_font * font = mswin_get_font(NHW_MESSAGE,
821                             data->window_text[MSG_LINES - 1].attr, hdc, FALSE);
822     saveFont = SelectObject(hdc, font->hFont);
823     GetClientRect(hWnd, &draw_rt);
824     draw_rt.left += LINE_PADDING_LEFT(data);
825     draw_rt.right -= LINE_PADDING_RIGHT(data);
826     draw_rt.bottom = draw_rt.top; /* we only need width for the DrawText */
827     DrawText(hdc, tmptext, strlen(tmptext), &draw_rt,
828              DT_NOPREFIX | DT_WORDBREAK | DT_CALCRECT);
829 
830     /* we will check against 1.5 of the font size in order to determine
831        if the text is single-line or not - just to be on the safe size */
832     retval = (draw_rt.bottom - draw_rt.top) < (data->yChar + data->yChar / 2);
833 
834     /* free device context */
835     SelectObject(hdc, saveFont);
836     ReleaseDC(hWnd, hdc);
837     return retval;
838 }
839 
840 /* check if "--more--" prompt needs to be displayed
841    basically, check if the lines not seen are going to find in the message
842    window
843 */
844 BOOL
more_prompt_check(HWND hWnd)845 more_prompt_check(HWND hWnd)
846 {
847     PNHMessageWindow data;
848     HDC hdc;
849     HGDIOBJ saveFont;
850     RECT client_rt, draw_rt;
851     int i;
852     int remaining_height;
853     char tmptext[MAXWINDOWTEXT + 1];
854 
855     data = (PNHMessageWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
856 
857     if (data->lines_not_seen == 0)
858         return FALSE; /* don't bother checking - nothig to "more" */
859     if (data->lines_not_seen >= MSG_LINES)
860         return TRUE; /* history size exceeded - always more */
861 
862     GetClientRect(hWnd, &client_rt);
863     remaining_height = client_rt.bottom - client_rt.top;
864 
865     hdc = GetDC(hWnd);
866     cached_font * font = mswin_get_font(NHW_MESSAGE, ATR_NONE, hdc, FALSE);
867     saveFont = SelectObject(hdc, font->hFont);
868     for (i = 0; i < data->lines_not_seen; i++) {
869         /* we only need width for the DrawText */
870         SetRect(&draw_rt,
871             client_rt.left + LINE_PADDING_LEFT(data), client_rt.top,
872             client_rt.right - LINE_PADDING_RIGHT(data), client_rt.top);
873         font = mswin_get_font(NHW_MESSAGE,
874                         data->window_text[MSG_LINES - i - 1].attr, hdc, FALSE);
875         SelectObject(hdc, font->hFont);
876 
877         strcpy(tmptext, data->window_text[MSG_LINES - i - 1].text);
878         strip_newline(tmptext);
879 
880         if (i == 0)
881             strcat(tmptext, MORE);
882 
883         remaining_height -=
884             DrawText(hdc, tmptext, strlen(tmptext), &draw_rt,
885                      DT_NOPREFIX | DT_WORDBREAK | DT_CALCRECT);
886         if (remaining_height <= 0)
887             break;
888     }
889 
890     /* free device context */
891     SelectObject(hdc, saveFont);
892     ReleaseDC(hWnd, hdc);
893     return (remaining_height
894             <= 0); /* TRUE if lines_not_seen take more that window height */
895 }
896