OSDN Git Service

fix #48255
[jnethack/source.git] / win / win32 / mhmsgwnd.c
1 /* NetHack 3.6  mhmsgwnd.c      $NHDT-Date: 1432512812 2015/05/25 00:13:32 $  $NHDT-Branch: master $:$NHDT-Revision: 1.32 $ */
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
69 mswin_init_message_window()
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
119 register_message_window_class()
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
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
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 #if 0 /*JP*/
272         char *p;
273 #else
274         unsigned char *p;
275 #endif
276
277         if (msg_data->append == 1) {
278             /* Forcibly append to line, even if we pass the edge */
279             strncat(data->window_text[MSG_LINES - 1].text, msg_data->text,
280                     MAXWINDOWTEXT
281                         - strlen(data->window_text[MSG_LINES - 1].text));
282         } else if (msg_data->append < 0) {
283             /* remove that many chars */
284             int len = strlen(data->window_text[MSG_LINES - 1].text);
285             int newend = max(len + msg_data->append, 0);
286             data->window_text[MSG_LINES - 1].text[newend] = '\0';
287         } else {
288             if (can_append_text(hWnd, msg_data->attr, msg_data->text)) {
289                 strncat(data->window_text[MSG_LINES - 1].text, "  ",
290                         MAXWINDOWTEXT
291                             - strlen(data->window_text[MSG_LINES - 1].text));
292                 strncat(data->window_text[MSG_LINES - 1].text, msg_data->text,
293                         MAXWINDOWTEXT
294                             - strlen(data->window_text[MSG_LINES - 1].text));
295             } else {
296                 /* check for "--more--" */
297                 if (!data->nevermore && more_prompt_check(hWnd)) {
298                     int okkey = 0;
299                     char tmptext[MAXWINDOWTEXT + 1];
300
301                     // @@@ Ok respnses
302
303                     /* save original text */
304                     strcpy(tmptext, data->window_text[MSG_LINES - 1].text);
305
306                     /* text could end in newline so strip it */
307                     strip_newline(data->window_text[MSG_LINES - 1].text);
308
309                     /* append more prompt and indicate the update */
310                     strncat(
311                         data->window_text[MSG_LINES - 1].text, MORE,
312                         MAXWINDOWTEXT
313                             - strlen(data->window_text[MSG_LINES - 1].text));
314                     InvalidateRect(hWnd, NULL, TRUE);
315
316                     /* get the input */
317                     while (!okkey) {
318                         int c = mswin_nhgetch();
319
320                         switch (c) {
321                         /* space or enter */
322                         case ' ':
323                         case '\015':
324                             okkey = 1;
325                             break;
326                         /* ESC */
327                         case '\033':
328                             data->nevermore = 1;
329                             okkey = 1;
330                             break;
331                         default:
332                             break;
333                         }
334                     }
335
336                     /* restore original text */
337                     strcpy(data->window_text[MSG_LINES - 1].text, tmptext);
338
339                     data->lines_not_seen = 0;
340                 }
341
342                 /* check if the string is empty */
343                 for (p = data->window_text[MSG_LINES - 1].text;
344                      *p && isspace((uchar) *p); p++)
345                     ;
346
347                 if (*p) {
348                     /* last string is not empty - scroll up */
349                     memmove(&data->window_text[0], &data->window_text[1],
350                             (MSG_LINES - 1) * sizeof(data->window_text[0]));
351                 }
352
353                 /* append new text to the end of the array */
354                 data->window_text[MSG_LINES - 1].attr = msg_data->attr;
355                 strncpy(data->window_text[MSG_LINES - 1].text, msg_data->text,
356                         MAXWINDOWTEXT);
357
358                 data->lines_not_seen++;
359                 data->lines_last_turn++;
360             }
361         }
362
363         /* reset V-scroll position to display new text */
364         data->yPos = data->yMax;
365
366         ZeroMemory(&si, sizeof(si));
367         si.cbSize = sizeof(si);
368         si.fMask = SIF_POS;
369         si.nPos = data->yPos;
370         SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
371
372         /* update window content */
373         InvalidateRect(hWnd, NULL, TRUE);
374
375 #ifdef USER_SOUNDS
376         if (!GetNHApp()->bNoSounds)
377             play_sound_for_message(msg_data->text);
378 #endif
379     } break;
380
381     case MSNH_MSG_CLEAR_WINDOW: {
382         data->lines_last_turn = 0;
383         data->lines_not_seen = 0;
384         data->nevermore = 0;
385         break;
386     }
387     case MSNH_MSG_CARET:
388         /* Create or destroy a caret */
389         if (*(int *) lParam)
390             CreateCaret(hWnd, NULL, 0, data->yChar);
391         else {
392             DestroyCaret();
393             /* this means we just did something interactive in this window, so
394                we
395                don't need a --More-- for the lines above.
396                */
397             data->lines_not_seen = 0;
398         }
399         break;
400
401     case MSNH_MSG_GETTEXT: {
402         PMSNHMsgGetText msg_data = (PMSNHMsgGetText) lParam;
403         int i;
404         size_t buflen;
405
406         buflen = 0;
407         for (i = 0; i < MSG_LINES; i++)
408             if (*data->window_text[i].text) {
409                 strncpy(&msg_data->buffer[buflen], data->window_text[i].text,
410                         msg_data->max_size - buflen);
411                 buflen += strlen(data->window_text[i].text);
412                 if (buflen >= msg_data->max_size)
413                     break;
414
415                 strncpy(&msg_data->buffer[buflen], "\r\n",
416                         msg_data->max_size - buflen);
417                 buflen += 2;
418                 if (buflen > msg_data->max_size)
419                     break;
420             }
421     } break;
422
423         case MSNH_MSG_RANDOM_INPUT:
424                 nhassert(0); // unexpected
425                 break;
426
427     } /* switch( wParam ) */
428 }
429
430 void
431 onMSNH_VScroll(HWND hWnd, WPARAM wParam, LPARAM lParam)
432 {
433     PNHMessageWindow data;
434     SCROLLINFO si;
435     int yInc;
436
437     UNREFERENCED_PARAMETER(lParam);
438
439     /* get window data */
440     data = (PNHMessageWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
441
442     ZeroMemory(&si, sizeof(si));
443     si.cbSize = sizeof(si);
444     si.fMask = SIF_PAGE | SIF_POS;
445     GetScrollInfo(hWnd, SB_VERT, &si);
446
447     switch (LOWORD(wParam)) {
448     // User clicked the shaft above the scroll box.
449
450     case SB_PAGEUP:
451         yInc = -(int) si.nPage;
452         break;
453
454     // User clicked the shaft below the scroll box.
455
456     case SB_PAGEDOWN:
457         yInc = si.nPage;
458         break;
459
460     // User clicked the top arrow.
461
462     case SB_LINEUP:
463         yInc = -1;
464         break;
465
466     // User clicked the bottom arrow.
467
468     case SB_LINEDOWN:
469         yInc = 1;
470         break;
471
472     // User dragged the scroll box.
473
474     case SB_THUMBTRACK:
475         yInc = HIWORD(wParam) - data->yPos;
476         break;
477
478     default:
479         yInc = 0;
480     }
481
482     // If applying the vertical scrolling increment does not
483     // take the scrolling position out of the scrolling range,
484     // increment the scrolling position, adjust the position
485     // of the scroll box, and update the window. UpdateWindow
486     // sends the WM_PAINT message.
487
488     if (yInc = max(MSG_VISIBLE_LINES - data->yPos,
489                    min(yInc, data->yMax - data->yPos))) {
490         data->yPos += yInc;
491         /* ScrollWindowEx(hWnd, 0, -data->yChar * yInc,
492                 (CONST RECT *) NULL, (CONST RECT *) NULL,
493                 (HRGN) NULL, (LPRECT) NULL, SW_INVALIDATE | SW_ERASE);
494         */
495         InvalidateRect(hWnd, NULL, TRUE);
496
497         ZeroMemory(&si, sizeof(si));
498         si.cbSize = sizeof(si);
499         si.fMask = SIF_POS;
500         si.nPos = data->yPos;
501         SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
502
503         UpdateWindow(hWnd);
504     }
505 }
506
507 #ifndef MSG_WRAP_TEXT
508 void
509 onMSNH_HScroll(HWND hWnd, WPARAM wParam, LPARAM lParam)
510 {
511     PNHMessageWindow data;
512     SCROLLINFO si;
513     int xInc;
514
515     /* get window data */
516     data = (PNHMessageWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
517
518     ZeroMemory(&si, sizeof(si));
519     si.cbSize = sizeof(si);
520     si.fMask = SIF_PAGE;
521     GetScrollInfo(hWnd, SB_HORZ, &si);
522
523     switch (LOWORD(wParam)) {
524     // User clicked shaft left of the scroll box.
525
526     case SB_PAGEUP:
527         xInc = -(int) si.nPage;
528         break;
529
530     // User clicked shaft right of the scroll box.
531
532     case SB_PAGEDOWN:
533         xInc = si.nPage;
534         break;
535
536     // User clicked the left arrow.
537
538     case SB_LINEUP:
539         xInc = -1;
540         break;
541
542     // User clicked the right arrow.
543
544     case SB_LINEDOWN:
545         xInc = 1;
546         break;
547
548     // User dragged the scroll box.
549
550     case SB_THUMBTRACK:
551         xInc = HIWORD(wParam) - data->xPos;
552         break;
553
554     default:
555         xInc = 0;
556     }
557
558     // If applying the horizontal scrolling increment does not
559     // take the scrolling position out of the scrolling range,
560     // increment the scrolling position, adjust the position
561     // of the scroll box, and update the window.
562
563     if (xInc = max(-data->xPos, min(xInc, data->xMax - data->xPos))) {
564         data->xPos += xInc;
565         ScrollWindowEx(hWnd, -data->xChar * xInc, 0, (CONST RECT *) NULL,
566                        (CONST RECT *) NULL, (HRGN) NULL, (LPRECT) NULL,
567                        SW_INVALIDATE | SW_ERASE);
568
569         ZeroMemory(&si, sizeof(si));
570         si.cbSize = sizeof(si);
571         si.fMask = SIF_POS;
572         si.nPos = data->xPos;
573         SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);
574         UpdateWindow(hWnd);
575     }
576 }
577 #endif // MSG_WRAP_TEXT
578
579 COLORREF
580 setMsgTextColor(HDC hdc, int gray)
581 {
582     COLORREF fg, color1, color2;
583     if (gray) {
584         if (message_bg_brush) {
585             color1 = message_bg_color;
586             color2 = message_fg_color;
587         } else {
588             color1 = (COLORREF) GetSysColor(DEFAULT_COLOR_BG_MSG);
589             color2 = (COLORREF) GetSysColor(DEFAULT_COLOR_FG_MSG);
590         }
591         /* Make a "gray" color by taking the average of the individual R,G,B
592            components of two colors. Thanks to Jonathan del Strother */
593         fg = RGB((GetRValue(color1) + GetRValue(color2)) / 2,
594                  (GetGValue(color1) + GetGValue(color2)) / 2,
595                  (GetBValue(color1) + GetBValue(color2)) / 2);
596     } else {
597         fg = message_fg_brush ? message_fg_color
598                               : (COLORREF) GetSysColor(DEFAULT_COLOR_FG_MSG);
599     }
600
601     return SetTextColor(hdc, fg);
602 }
603
604 void
605 onPaint(HWND hWnd)
606 {
607     PAINTSTRUCT ps;
608     HDC hdc;
609     PNHMessageWindow data;
610     RECT client_rt, draw_rt;
611     int FirstLine, LastLine;
612     int i, y;
613     HGDIOBJ oldFont;
614     TCHAR wbuf[MAXWINDOWTEXT + 2];
615     size_t wlen;
616     COLORREF OldBg, OldFg;
617
618     hdc = BeginPaint(hWnd, &ps);
619
620     OldBg = SetBkColor(
621         hdc, message_bg_brush ? message_bg_color
622                               : (COLORREF) GetSysColor(DEFAULT_COLOR_BG_MSG));
623     OldFg = setMsgTextColor(hdc, 0);
624
625     data = (PNHMessageWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
626
627     GetClientRect(hWnd, &client_rt);
628
629     if (!IsRectEmpty(&ps.rcPaint)) {
630         FirstLine = max(
631             0, data->yPos - (client_rt.bottom - ps.rcPaint.top) / data->yChar
632                    + 1);
633         LastLine =
634             min(MSG_LINES - 1,
635                 data->yPos
636                     - (client_rt.bottom - ps.rcPaint.bottom) / data->yChar);
637         y = min(ps.rcPaint.bottom, client_rt.bottom);
638         for (i = LastLine; i >= FirstLine; i--) {
639             char tmptext[MAXWINDOWTEXT + 1];
640
641             draw_rt.left = LINE_PADDING_LEFT(data);
642             draw_rt.right = client_rt.right - LINE_PADDING_RIGHT(data);
643             draw_rt.top = y - data->yChar;
644             draw_rt.bottom = y;
645
646             cached_font * font = mswin_get_font(NHW_MESSAGE,
647                                         data->window_text[i].attr, hdc, FALSE);
648             oldFont = SelectObject(hdc, font->hFont);
649
650             /* convert to UNICODE stripping newline */
651             strcpy(tmptext, data->window_text[i].text);
652             strip_newline(tmptext);
653             NH_A2W(tmptext, wbuf, sizeof(wbuf));
654             wlen = _tcslen(wbuf);
655             setMsgTextColor(hdc, i < (MSG_LINES - data->lines_last_turn));
656 #ifdef MSG_WRAP_TEXT
657             /* Find out how large the bounding rectangle of the text is */
658             DrawText(hdc, wbuf, wlen, &draw_rt,
659                      DT_NOPREFIX | DT_WORDBREAK | DT_CALCRECT);
660             /* move that rectangle up, so that the bottom remains at the same
661              * height */
662             draw_rt.top = y - (draw_rt.bottom - draw_rt.top);
663             draw_rt.bottom = y;
664
665             /* Now really draw it */
666             DrawText(hdc, wbuf, wlen, &draw_rt, DT_NOPREFIX | DT_WORDBREAK);
667
668             /* Find out the cursor (caret) position */
669             if (i == MSG_LINES - 1) {
670                 int nnum, numfit;
671                 SIZE size;
672                 TCHAR *nbuf;
673                 int nlen;
674
675                 nbuf = wbuf;
676                 nlen = wlen;
677                 while (nlen) {
678                     /* Get the number of characters that fit on the line */
679                     GetTextExtentExPoint(hdc, nbuf, nlen,
680                                          draw_rt.right - draw_rt.left,
681                                          &numfit, NULL, &size);
682                     /* Search back to a space */
683                     nnum = numfit;
684                     if (numfit < nlen) {
685                         while (nnum > 0 && nbuf[nnum] != ' ')
686                             nnum--;
687                         /* If no space found, break wherever */
688                         if (nnum == 0)
689                             nnum = numfit;
690                     }
691                     nbuf += nnum;
692                     nlen -= nnum;
693                     if (*nbuf == ' ') {
694                         nbuf++;
695                         nlen--;
696                     }
697                 }
698                 /* The last size is the size of the last line. Set the caret
699                    there.
700                    This will fail automatically if we don't own the caret
701                    (i.e.,
702                    when not in a question.)
703                  */
704                 SetCaretPos(draw_rt.left + size.cx,
705                             draw_rt.bottom - data->yChar);
706             }
707 #else
708             DrawText(hdc, wbuf, wlen, &draw_rt, DT_NOPREFIX);
709             SetCaretPos(draw_rt.left + size.cx, draw_rt.bottom - data->yChar);
710 #endif
711             SelectObject(hdc, oldFont);
712             y -= draw_rt.bottom - draw_rt.top;
713         }
714     }
715     SetTextColor(hdc, OldFg);
716     SetBkColor(hdc, OldBg);
717     EndPaint(hWnd, &ps);
718 }
719
720 void
721 onCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
722 {
723     PNHMessageWindow data;
724     SIZE dummy;
725
726     UNREFERENCED_PARAMETER(wParam);
727     UNREFERENCED_PARAMETER(lParam);
728
729     /* set window data */
730     data = (PNHMessageWindow) malloc(sizeof(NHMessageWindow));
731     if (!data)
732         panic("out of memory");
733     ZeroMemory(data, sizeof(NHMessageWindow));
734     data->max_text = MAXWINDOWTEXT;
735     SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) data);
736
737     /* re-calculate window size (+ font size) */
738     mswin_message_window_size(hWnd, &dummy);
739 }
740
741 void
742 mswin_message_window_size(HWND hWnd, LPSIZE sz)
743 {
744     HDC hdc;
745     HGDIOBJ saveFont;
746     TEXTMETRIC tm;
747     PNHMessageWindow data;
748     RECT rt, client_rt;
749
750     data = (PNHMessageWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
751     if (!data)
752         return;
753
754     /* -- Calculate the font size -- */
755     /* Get the handle to the client area's device context. */
756     hdc = GetDC(hWnd);
757     cached_font * font = mswin_get_font(NHW_MESSAGE, ATR_NONE, hdc, FALSE);
758     saveFont = SelectObject(hdc, font->hFont);
759
760     /* Extract font dimensions from the text metrics. */
761     GetTextMetrics(hdc, &tm);
762     data->xChar = tm.tmAveCharWidth;
763     data->xUpper = (tm.tmPitchAndFamily & 1 ? 3 : 2) * data->xChar / 2;
764     data->yChar = tm.tmHeight + tm.tmExternalLeading;
765     data->xPage = 1;
766
767     /* Free the device context.  */
768     SelectObject(hdc, saveFont);
769     ReleaseDC(hWnd, hdc);
770
771     /* -- calculate window size -- */
772     GetWindowRect(hWnd, &rt);
773     sz->cx = rt.right - rt.left;
774     sz->cy = rt.bottom - rt.top;
775
776     /* set size to accomodate MSG_VISIBLE_LINES and
777        horizontal scroll bar (difference between window rect and client rect
778        */
779     GetClientRect(hWnd, &client_rt);
780     sz->cy = sz->cy - (client_rt.bottom - client_rt.top)
781              + data->yChar * MSG_VISIBLE_LINES;
782 }
783
784 /* check if text can be appended to the last line without wrapping */
785 BOOL
786 can_append_text(HWND hWnd, int attr, const char *text)
787 {
788     PNHMessageWindow data;
789     char tmptext[MAXWINDOWTEXT + 1];
790     HDC hdc;
791     HGDIOBJ saveFont;
792     RECT draw_rt;
793     BOOL retval = FALSE;
794
795     data = (PNHMessageWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
796
797     /* cannot append if lines_not_seen is 0 (beginning of the new turn */
798     if (data->lines_not_seen == 0)
799         return FALSE;
800
801     /* cannot append text with different attrbutes */
802     if (data->window_text[MSG_LINES - 1].attr != attr)
803         return FALSE;
804
805     /* cannot append if current line ends in newline */
806     if (str_end_is(data->window_text[MSG_LINES - 1].text, "\n"))
807         return FALSE;
808
809     /* check if the maximum string langth will be exceeded */
810     if (strlen(data->window_text[MSG_LINES - 1].text) + 2
811             + /* space characters */
812             strlen(text) + strlen(MORE)
813         >= MAXWINDOWTEXT)
814         return FALSE;
815
816     /* check if the text is going to fit into a single line */
817     strcpy(tmptext, data->window_text[MSG_LINES - 1].text);
818     strcat(tmptext, "  ");
819     strcat(tmptext, text);
820     strip_newline(tmptext);
821     strcat(tmptext, MORE);
822
823     hdc = GetDC(hWnd);
824     cached_font * font = mswin_get_font(NHW_MESSAGE,
825                             data->window_text[MSG_LINES - 1].attr, hdc, FALSE);
826     saveFont = SelectObject(hdc, font->hFont);
827     GetClientRect(hWnd, &draw_rt);
828     draw_rt.left += LINE_PADDING_LEFT(data);
829     draw_rt.right -= LINE_PADDING_RIGHT(data);
830     draw_rt.bottom = draw_rt.top; /* we only need width for the DrawText */
831     DrawText(hdc, tmptext, strlen(tmptext), &draw_rt,
832              DT_NOPREFIX | DT_WORDBREAK | DT_CALCRECT);
833
834     /* we will check against 1.5 of the font size in order to determine
835        if the text is single-line or not - just to be on the safe size */
836     retval = (draw_rt.bottom - draw_rt.top) < (data->yChar + data->yChar / 2);
837
838     /* free device context */
839     SelectObject(hdc, saveFont);
840     ReleaseDC(hWnd, hdc);
841     return retval;
842 }
843
844 /* check if "--more--" prompt needs to be displayed
845    basically, check if the lines not seen are going to find in the message
846    window
847 */
848 BOOL
849 more_prompt_check(HWND hWnd)
850 {
851     PNHMessageWindow data;
852     HDC hdc;
853     HGDIOBJ saveFont;
854     RECT client_rt, draw_rt;
855     int i;
856     int remaining_height;
857     char tmptext[MAXWINDOWTEXT + 1];
858
859     data = (PNHMessageWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
860
861     if (data->lines_not_seen == 0)
862         return FALSE; /* don't bother checking - nothig to "more" */
863     if (data->lines_not_seen >= MSG_LINES)
864         return TRUE; /* history size exceeded - always more */
865
866     GetClientRect(hWnd, &client_rt);
867     remaining_height = client_rt.bottom - client_rt.top;
868
869     hdc = GetDC(hWnd);
870     cached_font * font = mswin_get_font(NHW_MESSAGE, ATR_NONE, hdc, FALSE);
871     saveFont = SelectObject(hdc, font->hFont);
872     for (i = 0; i < data->lines_not_seen; i++) {
873         /* we only need width for the DrawText */
874         SetRect(&draw_rt,
875             client_rt.left + LINE_PADDING_LEFT(data), client_rt.top,
876             client_rt.right - LINE_PADDING_RIGHT(data), client_rt.top);
877         font = mswin_get_font(NHW_MESSAGE,
878                         data->window_text[MSG_LINES - i - 1].attr, hdc, FALSE);
879         SelectObject(hdc, font->hFont);
880
881         strcpy(tmptext, data->window_text[MSG_LINES - i - 1].text);
882         strip_newline(tmptext);
883
884         if (i == 0)
885             strcat(tmptext, MORE);
886
887         remaining_height -=
888             DrawText(hdc, tmptext, strlen(tmptext), &draw_rt,
889                      DT_NOPREFIX | DT_WORDBREAK | DT_CALCRECT);
890         if (remaining_height <= 0)
891             break;
892     }
893
894     /* free device context */
895     SelectObject(hdc, saveFont);
896     ReleaseDC(hWnd, hdc);
897     return (remaining_height
898             <= 0); /* TRUE if lines_not_seen take more that window height */
899 }