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