1 /* Copyright (C) 2001 by Alex Kompel <shurikk@pacbell.net> */
2 /* NetHack may be freely redistributed. See license for details. */
11 #define MSG_VISIBLE_LINES max(iflags.wc_vary_msgcount, 2)
12 #define MAX_MSG_LINES 32
13 #define MSG_LINES (int)min(iflags.msg_history, MAX_MSG_LINES)
14 #define MAXWINDOWTEXT TBUFSZ
16 #define DEFAULT_COLOR_BG_MSG COLOR_WINDOW
17 #define DEFAULT_COLOR_FG_MSG COLOR_WINDOWTEXT
19 #define MORE "--More--"
23 char text[MAXWINDOWTEXT];
26 typedef struct mswin_nethack_message_window {
28 struct window_line window_text[MAX_MSG_LINES];
30 int window_text_lines[MAX_MSG_LINES]; /* How much space this text line takes */
32 int lines_last_turn; /* lines added during the last turn */
33 int cleared; /* clear was called */
34 int last_line; /* last line in the message history */
35 struct window_line new_line;
36 int lines_not_seen; /* lines not yet seen by user after last turn or --More-- */
37 int in_more; /* We are in a --More-- prompt */
38 int nevermore; /* We want no more --More-- prompts */
40 int xChar; /* horizontal scrolling unit */
41 int yChar; /* vertical scrolling unit */
42 int xUpper; /* average width of uppercase letters */
43 int xPos; /* current horizontal scrolling position */
44 int yPos; /* current vertical scrolling position */
45 int xMax; /* maximum horizontal scrolling position */
46 int yMax; /* maximum vertical scrolling position */
47 int xPage; /* page size of horizontal scroll bar */
48 } NHMessageWindow, *PNHMessageWindow;
50 static TCHAR szMessageWindowClass[] = TEXT("MSNHMessageWndClass");
51 LRESULT CALLBACK NHMessageWndProc(HWND, UINT, WPARAM, LPARAM);
52 static void register_message_window_class(void);
53 static void onMSNHCommand(HWND hWnd, WPARAM wParam, LPARAM lParam);
54 static void onMSNH_VScroll(HWND hWnd, WPARAM wParam, LPARAM lParam);
56 static void onMSNH_HScroll(HWND hWnd, WPARAM wParam, LPARAM lParam);
58 static COLORREF setMsgTextColor(HDC hdc, int gray);
59 static void onPaint(HWND hWnd);
60 static void onCreate(HWND hWnd, WPARAM wParam, LPARAM lParam);
63 extern void play_sound_for_message(const char* str);
66 HWND mswin_init_message_window () {
67 static int run_once = 0;
72 register_message_window_class( );
77 style = WS_CHILD | WS_CLIPSIBLINGS | WS_VSCROLL;
79 style = WS_CHILD | WS_CLIPSIBLINGS | WS_VSCROLL | WS_HSCROLL;
84 szMessageWindowClass, /* registered class name */
85 NULL, /* window name */
86 style, /* window style */
87 0, /* horizontal position of window */
88 0, /* vertical position of window */
90 0, /* window height - set it later */
91 GetNHApp()->hMainWnd, /* handle to parent or owner window */
92 NULL, /* menu handle or child identifier */
93 GetNHApp()->hApp, /* handle to application instance */
94 NULL ); /* window-creation data */
96 if( !ret ) panic("Cannot create message window");
101 void register_message_window_class()
104 ZeroMemory( &wcex, sizeof(wcex));
106 wcex.style = CS_NOCLOSE;
107 wcex.lpfnWndProc = (WNDPROC)NHMessageWndProc;
110 wcex.hInstance = GetNHApp()->hApp;
112 wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
113 wcex.hbrBackground = message_bg_brush ? message_bg_brush : SYSCLR_TO_BRUSH(DEFAULT_COLOR_BG_MSG);
114 wcex.lpszMenuName = NULL;
115 wcex.lpszClassName = szMessageWindowClass;
117 RegisterClass(&wcex);
120 LRESULT CALLBACK NHMessageWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
125 onCreate( hWnd, wParam, lParam );
128 case WM_MSNH_COMMAND:
129 onMSNHCommand(hWnd, wParam, lParam);
137 SetFocus(GetNHApp()->hMainWnd);
140 #ifndef MSG_WRAP_TEXT
142 onMSNH_HScroll(hWnd, wParam, lParam);
147 onMSNH_VScroll(hWnd, wParam, lParam);
152 PNHMessageWindow data;
153 data = (PNHMessageWindow)GetWindowLong(hWnd, GWL_USERDATA);
155 SetWindowLong(hWnd, GWL_USERDATA, (LONG)0);
163 PNHMessageWindow data;
165 data = (PNHMessageWindow)GetWindowLong(hWnd, GWL_USERDATA);
167 xNewSize = LOWORD(lParam);
168 yNewSize = HIWORD(lParam);
170 if( xNewSize>0 || yNewSize>0 ) {
172 #ifndef MSG_WRAP_TEXT
173 data->xPage = xNewSize/data->xChar;
174 data->xMax = max(0, (int)(1 + data->max_text - data->xPage));
175 data->xPos = min(data->xPos, data->xMax);
177 ZeroMemory(&si, sizeof(si));
178 si.cbSize = sizeof(si);
179 si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
181 si.nMax = data->max_text;
182 si.nPage = data->xPage;
183 si.nPos = data->xPos;
184 SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);
187 data->yMax = MSG_LINES-1;
188 data->yPos = min(data->yPos, data->yMax);
190 ZeroMemory(&si, sizeof(si));
191 si.cbSize = sizeof(si);
192 si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
193 si.nMin = MSG_VISIBLE_LINES;
194 si.nMax = data->yMax + MSG_VISIBLE_LINES - 1;
195 si.nPage = MSG_VISIBLE_LINES;
196 si.nPos = data->yPos;
197 SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
203 return DefWindowProc(hWnd, message, wParam, lParam);
208 void onMSNHCommand(HWND hWnd, WPARAM wParam, LPARAM lParam)
210 PNHMessageWindow data;
212 data = (PNHMessageWindow)GetWindowLong(hWnd, GWL_USERDATA);
214 case MSNH_MSG_PUTSTR:
216 PMSNHMsgPutstr msg_data = (PMSNHMsgPutstr)lParam;
219 if( msg_data->append == 1) {
220 /* Forcibly append to line, even if we pass the edge */
221 strncat(data->window_text[data->last_line].text, msg_data->text,
222 MAXWINDOWTEXT - strlen(data->window_text[data->last_line].text));
223 } else if( msg_data->append < 0) {
224 /* remove that many chars */
225 int len = strlen(data->window_text[data->last_line].text);
226 int newend = max(len + msg_data->append, 0);
227 data->window_text[data->last_line].text[newend] = '\0';
229 /* Try to append but move the whole message to the next line if
231 /* just schedule for displaying */
232 data->new_line.attr = msg_data->attr;
233 strncpy(data->new_line.text, msg_data->text, MAXWINDOWTEXT);
236 /* reset V-scroll position to display new text */
237 data->yPos = data->yMax;
239 ZeroMemory(&si, sizeof(si));
240 si.cbSize = sizeof(si);
242 si.nPos = data->yPos;
243 SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
245 /* update window content */
246 InvalidateRect(hWnd, NULL, TRUE);
249 play_sound_for_message(msg_data->text);
254 case MSNH_MSG_CLEAR_WINDOW:
257 data->lines_not_seen = 0;
258 /* do --More-- again if needed */
263 /* Create or destroy a caret */
265 CreateCaret(hWnd, NULL, 0, data->yChar);
268 /* this means we just did something interactive in this window, so we
269 don't need a --More-- for the lines above.
271 data->lines_not_seen = 0;
279 void onMSNH_VScroll(HWND hWnd, WPARAM wParam, LPARAM lParam)
281 PNHMessageWindow data;
285 /* get window data */
286 data = (PNHMessageWindow)GetWindowLong(hWnd, GWL_USERDATA);
288 ZeroMemory(&si, sizeof(si));
289 si.cbSize = sizeof(si);
290 si.fMask = SIF_PAGE | SIF_POS;
291 GetScrollInfo(hWnd, SB_VERT, &si);
293 switch(LOWORD (wParam))
295 // User clicked the shaft above the scroll box.
298 yInc = -(int)si.nPage;
301 // User clicked the shaft below the scroll box.
307 // User clicked the top arrow.
313 // User clicked the bottom arrow.
319 // User dragged the scroll box.
322 yInc = HIWORD(wParam) - data->yPos;
329 // If applying the vertical scrolling increment does not
330 // take the scrolling position out of the scrolling range,
331 // increment the scrolling position, adjust the position
332 // of the scroll box, and update the window. UpdateWindow
333 // sends the WM_PAINT message.
335 if (yInc = max( MSG_VISIBLE_LINES - data->yPos,
336 min(yInc, data->yMax - data->yPos)))
339 /* ScrollWindowEx(hWnd, 0, -data->yChar * yInc,
340 (CONST RECT *) NULL, (CONST RECT *) NULL,
341 (HRGN) NULL, (LPRECT) NULL, SW_INVALIDATE | SW_ERASE);
343 InvalidateRect(hWnd, NULL, TRUE);
345 ZeroMemory(&si, sizeof(si));
346 si.cbSize = sizeof(si);
348 si.nPos = data->yPos;
349 SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
355 #ifndef MSG_WRAP_TEXT
356 void onMSNH_HScroll(HWND hWnd, WPARAM wParam, LPARAM lParam)
358 PNHMessageWindow data;
362 /* get window data */
363 data = (PNHMessageWindow)GetWindowLong(hWnd, GWL_USERDATA);
365 ZeroMemory(&si, sizeof(si));
366 si.cbSize = sizeof(si);
368 GetScrollInfo(hWnd, SB_HORZ, &si);
370 switch(LOWORD (wParam))
372 // User clicked shaft left of the scroll box.
375 xInc = - (int)si.nPage;
378 // User clicked shaft right of the scroll box.
384 // User clicked the left arrow.
390 // User clicked the right arrow.
396 // User dragged the scroll box.
399 xInc = HIWORD(wParam) - data->xPos;
408 // If applying the horizontal scrolling increment does not
409 // take the scrolling position out of the scrolling range,
410 // increment the scrolling position, adjust the position
411 // of the scroll box, and update the window.
413 if (xInc = max (-data->xPos, min (xInc, data->xMax - data->xPos)))
416 ScrollWindowEx (hWnd, -data->xChar * xInc, 0,
417 (CONST RECT *) NULL, (CONST RECT *) NULL,
418 (HRGN) NULL, (LPRECT) NULL, SW_INVALIDATE | SW_ERASE);
420 ZeroMemory(&si, sizeof(si));
421 si.cbSize = sizeof(si);
423 si.nPos = data->xPos;
424 SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);
428 #endif // MSG_WRAP_TEXT
430 COLORREF setMsgTextColor(HDC hdc, int gray)
432 COLORREF fg, color1, color2;
434 if (message_bg_brush) {
435 color1 = message_bg_color;
436 color2 = message_fg_color;
438 color1 = (COLORREF)GetSysColor(DEFAULT_COLOR_BG_MSG);
439 color2 = (COLORREF)GetSysColor(DEFAULT_COLOR_FG_MSG);
441 /* Make a "gray" color by taking the average of the individual R,G,B
442 components of two colors. Thanks to Jonathan del Strother */
443 fg = RGB((GetRValue(color1)+GetRValue(color2))/2,
444 (GetGValue(color1)+GetGValue(color2))/2,
445 (GetBValue(color1)+GetBValue(color2))/2);
447 fg = message_fg_brush ? message_fg_color : (COLORREF)GetSysColor(DEFAULT_COLOR_FG_MSG);
451 return SetTextColor(hdc, fg);
455 void onPaint(HWND hWnd)
459 PNHMessageWindow data;
460 RECT client_rt, draw_rt;
461 int FirstLine, LastLine;
464 TCHAR wbuf[MAXWINDOWTEXT+2];
466 COLORREF OldBg, OldFg;
469 hdc = BeginPaint(hWnd, &ps);
471 OldBg = SetBkColor(hdc, message_bg_brush ? message_bg_color : (COLORREF)GetSysColor(DEFAULT_COLOR_BG_MSG));
472 OldFg = setMsgTextColor(hdc, 0);
474 data = (PNHMessageWindow)GetWindowLong(hWnd, GWL_USERDATA);
476 GetClientRect(hWnd, &client_rt);
478 if( !IsRectEmpty(&ps.rcPaint) ) {
479 FirstLine = max (0, data->yPos - (client_rt.bottom - ps.rcPaint.top)/data->yChar + 1);
480 LastLine = min (MSG_LINES-1, data->yPos - (client_rt.bottom - ps.rcPaint.bottom)/data->yChar);
481 y = min( ps.rcPaint.bottom, client_rt.bottom );
482 for (i=LastLine; i>=FirstLine; i--) {
483 int lineidx = (data->last_line + 1 + i) % MSG_LINES;
484 x = data->xChar * (2 - data->xPos);
487 draw_rt.right = client_rt.right;
488 draw_rt.top = y - data->yChar;
491 oldFont = SelectObject(hdc, mswin_get_font(NHW_MESSAGE, data->window_text[lineidx].attr, hdc, FALSE));
493 /* find out if we can concatenate the scheduled message without wrapping,
494 but only if no clear_nhwindow was done just before putstr'ing this one,
495 and only if not in a more prompt already (to prevent concatenating to
496 a line containing --More-- when resizing while --More-- is displayed.)
499 && strlen(data->new_line.text) > 0
501 /* concatenate to the previous line if that is not empty, and
502 if it has the same attribute, and no clear was done.
504 if (strlen(data->window_text[lineidx].text) > 0
505 && (data->window_text[lineidx].attr
506 == data->new_line.attr)
508 RECT tmpdraw_rt = draw_rt;
509 /* assume this will never work when textsize is near MAXWINDOWTEXT */
510 char tmptext[MAXWINDOWTEXT];
511 TCHAR tmpwbuf[MAXWINDOWTEXT+2];
513 strcpy(tmptext, data->window_text[lineidx].text);
514 strncat(tmptext, " ",
515 MAXWINDOWTEXT - strlen(tmptext));
516 strncat(tmptext, data->new_line.text,
517 MAXWINDOWTEXT - strlen(tmptext));
518 /* Always keep room for a --More-- */
519 strncat(tmptext, MORE,
520 MAXWINDOWTEXT - strlen(tmptext));
521 NH_A2W(tmptext, tmpwbuf, sizeof(tmpwbuf));
522 /* Find out how large the bounding rectangle of the text is */
523 DrawText(hdc, tmpwbuf, _tcslen(tmpwbuf), &tmpdraw_rt, DT_NOPREFIX | DT_WORDBREAK | DT_CALCRECT);
524 if ((tmpdraw_rt.bottom - tmpdraw_rt.top) == (draw_rt.bottom - draw_rt.top) /* fits pixelwise */
525 && (strlen(data->window_text[lineidx].text)
526 + strlen(data->new_line.text) < MAXWINDOWTEXT)) /* fits charwise */
528 /* strip off --More-- of this combined line and make it so */
529 tmptext[strlen(tmptext) - strlen(MORE)] = '\0';
530 strcpy(data->window_text[lineidx].text, tmptext);
531 data->new_line.text[0] = '\0';
532 i++; /* Start from the last line again */
536 if (strlen(data->new_line.text) > 0) {
537 /* if we get here, the new line was not concatenated. Add it on a new line,
538 but first check whether we should --More--. */
539 RECT tmpdraw_rt = draw_rt;
540 TCHAR tmpwbuf[MAXWINDOWTEXT+2];
542 int new_screen_lines;
543 int screen_lines_not_seen = 0;
544 /* Count how many screen lines we haven't seen yet. */
548 for (n = data->lines_not_seen - 1; n >= 0; n--) {
549 screen_lines_not_seen +=
550 data->window_text_lines[(data->last_line - n + MSG_LINES) % MSG_LINES];
554 screen_lines_not_seen = data->lines_not_seen;
556 /* Now find out how many screen lines we would like to add */
557 NH_A2W(data->new_line.text, tmpwbuf, sizeof(tmpwbuf));
558 /* Find out how large the bounding rectangle of the text is */
559 oldFont = SelectObject(hdc, mswin_get_font(NHW_MESSAGE, data->window_text[lineidx].attr, hdc, FALSE));
560 DrawText(hdc, tmpwbuf, _tcslen(tmpwbuf), &tmpdraw_rt, DT_NOPREFIX | DT_WORDBREAK | DT_CALCRECT);
561 SelectObject(hdc, oldFont);
562 new_screen_lines = (tmpdraw_rt.bottom - tmpdraw_rt.top) / data->yChar;
563 /* If this together is more than fits on the window, we must
565 - We are in --More-- already (the user is scrolling the window)
566 - The user pressed ESC
568 if (screen_lines_not_seen + new_screen_lines > MSG_VISIBLE_LINES
569 && !data->in_more && !data->nevermore) {
571 /* Show --More-- on last line */
572 strcat(data->window_text[data->last_line].text, MORE);
573 /* Go on drawing, but remember we must do a more afterwards */
575 } else if (!data->in_more) {
577 data->last_line %= MSG_LINES;
578 data->window_text[data->last_line].attr = data->new_line.attr;
579 strncpy(data->window_text[data->last_line].text, data->new_line.text, MAXWINDOWTEXT);
580 data->new_line.text[0] = '\0';
582 /* now we are drawing a new line, the old lines can be redrawn in grey.*/
583 data->lines_last_turn = 0;
586 data->lines_last_turn++;
587 data->lines_not_seen++;
589 i++; /* Start from the last line again */
594 /* convert to UNICODE */
595 NH_A2W(data->window_text[lineidx].text, wbuf, sizeof(wbuf));
596 wlen = _tcslen(wbuf);
597 setMsgTextColor(hdc, i < (MSG_LINES - data->lines_last_turn));
599 /* Find out how large the bounding rectangle of the text is */
600 DrawText(hdc, wbuf, wlen, &draw_rt, DT_NOPREFIX | DT_WORDBREAK | DT_CALCRECT);
601 /* move that rectangle up, so that the bottom remains at the same height */
602 draw_rt.top = y - (draw_rt.bottom - draw_rt.top);
604 /* Remember the height of this line for subsequent --More--'s */
605 data->window_text_lines[lineidx] = (draw_rt.bottom - draw_rt.top) / data->yChar;
606 /* Now really draw it */
607 DrawText(hdc, wbuf, wlen, &draw_rt, DT_NOPREFIX | DT_WORDBREAK);
609 /* Find out the cursor (caret) position */
610 if (i == MSG_LINES-1) {
619 /* Get the number of characters that fit on the line */
620 GetTextExtentExPoint(hdc, nbuf, nlen, draw_rt.right - draw_rt.left, &numfit, NULL, &size);
621 /* Search back to a space */
624 while (nnum > 0 && nbuf[nnum] != ' ')
626 /* If no space found, break wherever */
637 /* The last size is the size of the last line. Set the caret there.
638 This will fail automatically if we don't own the caret (i.e.,
639 when not in a question.)
641 SetCaretPos(draw_rt.left + size.cx, draw_rt.bottom - data->yChar);
644 DrawText(hdc, wbuf, wlen, &draw_rt, DT_NOPREFIX );
645 SetCaretPos(draw_rt.left + size.cx, draw_rt.bottom - data->yChar);
647 SelectObject(hdc, oldFont);
648 y -= draw_rt.bottom - draw_rt.top;
656 char c = mswin_nhgetch();
674 chop = strlen(data->window_text[data->last_line].text)
676 data->window_text[data->last_line].text[chop] = '\0';
678 data->lines_not_seen = 0;
679 /* We did the --More--, reset the lines_not_seen; now draw that
680 new line. This is the easiest method */
681 InvalidateRect(hWnd, NULL, TRUE);
684 SetTextColor (hdc, OldFg);
685 SetBkColor (hdc, OldBg);
689 void onCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
691 PNHMessageWindow data;
694 /* set window data */
695 data = (PNHMessageWindow)malloc(sizeof(NHMessageWindow));
696 if( !data ) panic("out of memory");
697 ZeroMemory(data, sizeof(NHMessageWindow));
698 data->max_text = MAXWINDOWTEXT;
699 SetWindowLong(hWnd, GWL_USERDATA, (LONG)data);
701 /* re-calculate window size (+ font size) */
702 mswin_message_window_size(hWnd, &dummy);
705 void mswin_message_window_size (HWND hWnd, LPSIZE sz)
710 PNHMessageWindow data;
713 data = (PNHMessageWindow)GetWindowLong(hWnd, GWL_USERDATA);
716 /* -- Calculate the font size -- */
717 /* Get the handle to the client area's device context. */
719 saveFont = SelectObject(hdc, mswin_get_font(NHW_MESSAGE, ATR_NONE, hdc, FALSE));
721 /* Extract font dimensions from the text metrics. */
722 GetTextMetrics (hdc, &tm);
723 data->xChar = tm.tmAveCharWidth;
724 data->xUpper = (tm.tmPitchAndFamily & 1 ? 3 : 2) * data->xChar/2;
725 data->yChar = tm.tmHeight + tm.tmExternalLeading;
728 /* Free the device context. */
729 SelectObject(hdc, saveFont);
730 ReleaseDC (hWnd, hdc);
732 /* -- calculate window size -- */
733 GetWindowRect(hWnd, &rt);
734 sz->cx = rt.right - rt.left;
735 sz->cy = rt.bottom - rt.top;
737 /* set size to accomodate MSG_VISIBLE_LINES and
738 horizontal scroll bar (difference between window rect and client rect */
739 GetClientRect(hWnd, &client_rt);
740 sz->cy = sz->cy - (client_rt.bottom - client_rt.top) +
741 data->yChar * MSG_VISIBLE_LINES;