OSDN Git Service

upgrade to 3.6.2
[jnethack/source.git] / win / win32 / mhmap.c
1 /* NetHack 3.6  mhmap.c $NHDT-Date: 1435002695 2015/06/22 19:51:35 $  $NHDT-Branch: master $:$NHDT-Revision: 1.56 $ */
2 /* Copyright (C) 2001 by Alex Kompel      */
3 /* NetHack may be freely redistributed.  See license for details. */
4
5 #include "win10.h"
6 #include "winMS.h"
7 #include "winos.h"
8
9 #include "mhfont.h"
10 #include "mhinput.h"
11 #include "mhmap.h"
12 #include "mhmsg.h"
13 #include "resource.h"
14
15 #include "color.h"
16 #include "patchlevel.h"
17
18 #define NHMAP_FONT_NAME TEXT("Terminal")
19 #define NHMAP_TTFONT_NAME TEXT("Consolas")
20 #define MAXWINDOWTEXT 255
21
22 #define CURSOR_BLINK_INTERVAL 1000 // milliseconds
23 #define CURSOR_HEIGHT 2 // pixels
24
25 extern short glyph2tile[];
26
27 #define TILEBMP_X(ntile) \
28     ((ntile % GetNHApp()->mapTilesPerLine) * GetNHApp()->mapTile_X)
29 #define TILEBMP_Y(ntile) \
30     ((ntile / GetNHApp()->mapTilesPerLine) * GetNHApp()->mapTile_Y)
31
32 /* map window data */
33 typedef struct mswin_nethack_map_window {
34     HWND hWnd;                  /* window */
35
36     int map[COLNO][ROWNO];      /* glyph map */
37     int bkmap[COLNO][ROWNO];    /* backround glyph map */
38     boolean mapDirty[COLNO][ROWNO]; /* dirty flag for map */
39
40     int mapMode;                /* current map mode */
41     boolean bAsciiMode;         /* switch ASCII/tiled mode */
42     boolean bFitToScreenMode;   /* switch Fit map to screen mode on/off */
43     int xPos, yPos;             /* scroll position */
44     int xPageSize, yPageSize;   /* scroll page size */
45     int xMin, xMax, yMin, yMax; /* scroll range */
46     int xCur, yCur;             /* position of the cursor */
47     int xFrontTile, yFrontTile; /* size of tile in front buffer in pixels */
48     int xBackTile, yBackTile;   /* size of tile in back buffer in pixels */
49     POINT map_orig;             /* map origin point */
50
51     HFONT hMapFont;             /* font for ASCII mode */
52     boolean bUnicodeFont;       /* font supports unicode page 437 */
53
54     int tileWidth;              /* width of tile in pixels at 96 dpi */
55     int tileHeight;             /* height of tile in pixels at 96 dpi */
56     double backScale;           /* scaling from source to back buffer */
57     double frontScale;          /* scaling from back to front */
58     double monitorScale;        /* from 96dpi to monitor dpi*/
59     
60     boolean cursorOn;
61     int yNoBlinkCursor;         /* non-blinking cursor height inback buffer
62                                    in pixels */
63     int yBlinkCursor;           /* blinking cursor height inback buffer
64                                    in pixels */
65
66     int backWidth;              /* back buffer width */
67     int backHeight;             /* back buffer height */
68     HBITMAP hBackBuffer;        /* back buffe bitmap */
69     HDC backBufferDC;          /* back buffer drawing context */
70
71     HDC tileDC;                /* tile drawing context */
72
73 } NHMapWindow, *PNHMapWindow;
74
75 static TCHAR szNHMapWindowClass[] = TEXT("MSNethackMapWndClass");
76 LRESULT CALLBACK MapWndProc(HWND, UINT, WPARAM, LPARAM);
77 static void register_map_window_class(void);
78 static void onMSNHCommand(HWND hWnd, WPARAM wParam, LPARAM lParam);
79 static void onMSNH_VScroll(HWND hWnd, WPARAM wParam, LPARAM lParam);
80 static void onMSNH_HScroll(HWND hWnd, WPARAM wParam, LPARAM lParam);
81 static void onPaint(HWND hWnd);
82 static void onCreate(HWND hWnd, WPARAM wParam, LPARAM lParam);
83 static void nhcoord2display(PNHMapWindow data, int x, int y, LPRECT lpOut);
84 static void paint(PNHMapWindow data, int i, int j);
85 static void dirtyAll(PNHMapWindow data);
86 static void dirty(PNHMapWindow data, int i, int j);
87 static void setGlyph(PNHMapWindow data, int i, int j, int fg, int bg);
88 static void clearAll(PNHMapWindow data);
89
90 #if (VERSION_MAJOR < 4) && (VERSION_MINOR < 4) && (PATCHLEVEL < 2)
91 static void nhglyph2charcolor(short glyph, uchar *ch, int *color);
92 #endif
93 extern boolean win32_cursorblink;       /* from sys\winnt\winnt.c */
94
95 HWND
96 mswin_init_map_window()
97 {
98     static int run_once = 0;
99     HWND hWnd;
100     RECT rt;
101
102     if (!run_once) {
103         register_map_window_class();
104         run_once = 1;
105     }
106
107     /* get window position */
108     if (GetNHApp()->bAutoLayout) {
109         SetRect(&rt, 0, 0, 0, 0);
110     } else {
111         mswin_get_window_placement(NHW_MAP, &rt);
112     }
113
114     /* create map window object */
115     hWnd = CreateWindow(
116         szNHMapWindowClass, /* registered class name */
117         NULL,               /* window name */
118         WS_CHILD | WS_HSCROLL | WS_VSCROLL | WS_CLIPSIBLINGS
119             | WS_SIZEBOX,     /* window style */
120         rt.left,              /* horizontal position of window */
121         rt.top,               /* vertical position of window */
122         rt.right - rt.left,   /* window width */
123         rt.bottom - rt.top,   /* window height */
124         GetNHApp()->hMainWnd, /* handle to parent or owner window */
125         NULL,                 /* menu handle or child identifier */
126         GetNHApp()->hApp,     /* handle to application instance */
127         NULL);                /* window-creation data */
128     if (!hWnd) {
129         panic("Cannot create map window");
130     }
131
132     /* Set window caption */
133     SetWindowText(hWnd, "Map");
134
135     mswin_apply_window_style(hWnd);
136
137     /* set cursor blink timer */
138     SetTimer(hWnd, 0, CURSOR_BLINK_INTERVAL, NULL);
139
140     return hWnd;
141 }
142
143 void
144 mswin_map_stretch(HWND hWnd, LPSIZE map_size, BOOL redraw)
145 {
146     /* check arguments */
147     if (!IsWindow(hWnd) || !map_size || map_size->cx <= 0
148         || map_size->cy <= 0)
149         return;
150
151     PNHMapWindow data = (PNHMapWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
152
153     /* calculate window size */
154     RECT client_rt;
155     GetClientRect(hWnd, &client_rt);
156
157     SIZE wnd_size;
158     wnd_size.cx = client_rt.right - client_rt.left;
159     wnd_size.cy = client_rt.bottom - client_rt.top;
160
161     // calculate back buffer scale
162     data->monitorScale = win10_monitor_scale(hWnd);
163
164     boolean bText = data->bAsciiMode ||
165                     (u.uz.dlevel != 0 && Is_rogue_level(&u.uz));
166
167     if (bText && !data->bFitToScreenMode)
168         data->backScale = data->monitorScale;
169     else
170         data->backScale = 1.0;
171
172     /* set back buffer tile size */
173     if (bText && data->bFitToScreenMode) {
174         data->xBackTile = wnd_size.cx / COLNO;
175         data->yBackTile = wnd_size.cy / ROWNO;
176         data->yBackTile = max(data->yBackTile, 12);
177     } else {
178         data->xBackTile = (int)(data->tileWidth * data->backScale);
179         data->yBackTile = (int)(data->tileHeight * data->backScale);
180     }
181
182     if (bText) {
183         LOGFONT lgfnt;
184
185         ZeroMemory(&lgfnt, sizeof(lgfnt));
186         if (data->bFitToScreenMode) {
187             lgfnt.lfHeight = -data->yBackTile;     // height of font
188             lgfnt.lfWidth = 0;                     // average character width
189         } else {
190             lgfnt.lfHeight = -data->yBackTile;     // height of font
191             lgfnt.lfWidth = -data->xBackTile;      // average character width
192         }
193         lgfnt.lfEscapement = 0;                    // angle of escapement
194         lgfnt.lfOrientation = 0;                   // base-line orientation angle
195         lgfnt.lfWeight = FW_NORMAL;                // font weight
196         lgfnt.lfItalic = FALSE;                    // italic attribute option
197         lgfnt.lfUnderline = FALSE;                 // underline attribute option
198         lgfnt.lfStrikeOut = FALSE;                 // strikeout attribute option
199         lgfnt.lfCharSet = mswin_charset();         // character set identifier
200         lgfnt.lfOutPrecision = OUT_DEFAULT_PRECIS; // output precision
201         lgfnt.lfClipPrecision = CLIP_DEFAULT_PRECIS; // clipping precision
202         if (data->bFitToScreenMode) {
203             lgfnt.lfQuality = ANTIALIASED_QUALITY; // output quality
204         } else {
205             lgfnt.lfQuality = NONANTIALIASED_QUALITY; // output quality
206         }
207         if (iflags.wc_font_map && *iflags.wc_font_map) {
208             lgfnt.lfPitchAndFamily = DEFAULT_PITCH; // pitch and family
209             NH_A2W(iflags.wc_font_map, lgfnt.lfFaceName, LF_FACESIZE);
210         } else {
211             if (!data->bFitToScreenMode) {
212                 lgfnt.lfPitchAndFamily = FIXED_PITCH; // pitch and family
213                 NH_A2W(NHMAP_FONT_NAME, lgfnt.lfFaceName, LF_FACESIZE);
214             } else {
215                 lgfnt.lfPitchAndFamily = DEFAULT_PITCH; // pitch and family
216                 NH_A2W(NHMAP_TTFONT_NAME, lgfnt.lfFaceName, LF_FACESIZE);
217             }
218         }
219
220         TEXTMETRIC textMetrics;
221         HFONT font = NULL;
222
223         while (1) {
224
225             if (font != NULL)
226                 DeleteObject(font);
227
228             font = CreateFontIndirect(&lgfnt);
229
230             SelectObject(data->backBufferDC, font);
231
232             GetTextMetrics(data->backBufferDC, &textMetrics);
233
234             if (!data->bFitToScreenMode)
235                 break;
236
237             if ((textMetrics.tmHeight > data->yBackTile ||
238                  textMetrics.tmAveCharWidth > data->xBackTile) &&
239                 lgfnt.lfHeight < -MIN_FONT_HEIGHT) {
240                 lgfnt.lfHeight++;
241                 continue;
242             }
243
244             break;
245         }
246
247         if (data->hMapFont)
248             DeleteObject(data->hMapFont);
249
250         data->hMapFont = font;
251
252         data->bUnicodeFont = winos_font_support_cp437(data->hMapFont);
253
254         // set tile size to match font metrics
255
256         data->xBackTile = textMetrics.tmAveCharWidth;
257         data->yBackTile = textMetrics.tmHeight;
258
259     }
260
261     int backWidth = COLNO * data->xBackTile;
262     int backHeight = ROWNO * data->yBackTile;
263
264     /* create back buffer */
265
266     if (data->backWidth != backWidth || data->backHeight != backHeight) {
267
268         HDC frontBufferDC = GetDC(hWnd);
269         HBITMAP hBackBuffer = CreateCompatibleBitmap(frontBufferDC, backWidth, backHeight);
270         ReleaseDC(hWnd, frontBufferDC);
271         
272         if (data->hBackBuffer != NULL) {
273             SelectBitmap(data->backBufferDC, hBackBuffer);
274             DeleteObject(data->hBackBuffer);
275         }
276
277         data->backWidth = backWidth;
278         data->backHeight = backHeight;
279
280         SelectBitmap(data->backBufferDC, hBackBuffer);
281         data->hBackBuffer = hBackBuffer;
282     }
283
284     /* calculate front buffer tile size */
285
286     if (wnd_size.cx > 0 && wnd_size.cy > 0 && !bText && data->bFitToScreenMode) {
287         double windowAspectRatio =
288             (double) wnd_size.cx / (double) wnd_size.cy;
289
290         double backAspectRatio = 
291             (double) data->backWidth / (double) data->backHeight;
292
293         if (windowAspectRatio > backAspectRatio)
294             data->frontScale = (double) wnd_size.cy / (double) data->backHeight;
295         else
296             data->frontScale = (double) wnd_size.cx / (double) data->backWidth;
297
298     } else {
299
300         if (bText) {
301             data->frontScale = 1.0;
302         } else {
303             data->frontScale = data->monitorScale;
304         }
305
306     }
307
308     data->xFrontTile = (int) ((double) data->xBackTile * data->frontScale);
309     data->yFrontTile = (int) ((double) data->yBackTile * data->frontScale);
310
311     /* calcuate ASCII cursor height */
312     data->yBlinkCursor = (int) ((double) CURSOR_HEIGHT * data->backScale);
313     data->yNoBlinkCursor = data->yBackTile;
314
315     /* set map origin point */
316     data->map_orig.x =
317         max(0, client_rt.left + (wnd_size.cx - data->xFrontTile * COLNO) / 2);
318     data->map_orig.y =
319         max(0, client_rt.top + (wnd_size.cy - data->yFrontTile * ROWNO) / 2);
320
321     data->map_orig.x -= data->map_orig.x % data->xFrontTile;
322     data->map_orig.y -= data->map_orig.y % data->yFrontTile;
323
324     // Set horizontal scroll
325
326     data->xPageSize = min(COLNO, wnd_size.cx / data->xFrontTile);
327
328     GetNHApp()->bNoHScroll = (data->xPageSize == COLNO);
329
330     data->xMin = 0;
331     data->xMax = COLNO - data->xPageSize;
332     data->xPos = max(0, min(data->xMax, u.ux - (data->xPageSize / 2)));
333
334     SCROLLINFO si;
335
336     si.cbSize = sizeof(si);
337     si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
338     si.nMin = data->xMin;
339     si.nMax = data->xMax;
340     si.nPage = 1;
341     si.nPos = data->xPos;
342     SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);
343
344     data->yPageSize = min(ROWNO, wnd_size.cy / data->yFrontTile);
345
346     GetNHApp()->bNoVScroll = (data->yPageSize == ROWNO);
347
348     data->yMin = 0;
349     data->yMax = ROWNO - data->yPageSize;
350     data->yPos = max(0, min(data->yMax, u.uy - (data->yPageSize / 2)));
351
352     si.cbSize = sizeof(si);
353     si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
354     si.nMin = data->yMin;
355     si.nMax = data->yMax;
356     si.nPage = 1;
357     si.nPos = data->yPos;
358     SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
359
360     mswin_cliparound(data->xCur, data->yCur);
361
362     if (redraw) {
363         dirtyAll(data);
364         InvalidateRect(hWnd, NULL, TRUE);
365     }
366 }
367
368 /* set map mode */
369 int
370 mswin_map_mode(HWND hWnd, int mode)
371 {
372     PNHMapWindow data;
373     int oldMode;
374     SIZE mapSize;
375
376     data = (PNHMapWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
377     if (mode == data->mapMode)
378         return mode;
379
380     oldMode = data->mapMode;
381     data->mapMode = mode;
382
383     switch (data->mapMode) {
384     case MAP_MODE_ASCII4x6:
385         data->bAsciiMode = TRUE;
386         data->bFitToScreenMode = FALSE;
387         data->tileWidth = 4;
388         data->tileHeight = 6;
389         break;
390
391     case MAP_MODE_ASCII6x8:
392         data->bAsciiMode = TRUE;
393         data->bFitToScreenMode = FALSE;
394         data->tileWidth = 6;
395         data->tileHeight = 8;
396         break;
397
398     case MAP_MODE_ASCII8x8:
399         data->bAsciiMode = TRUE;
400         data->bFitToScreenMode = FALSE;
401         data->tileWidth = 8;
402         data->tileHeight = 8;
403         break;
404
405     case MAP_MODE_ASCII16x8:
406         data->bAsciiMode = TRUE;
407         data->bFitToScreenMode = FALSE;
408         data->tileWidth = 16;
409         data->tileHeight = 8;
410         break;
411
412     case MAP_MODE_ASCII7x12:
413         data->bAsciiMode = TRUE;
414         data->bFitToScreenMode = FALSE;
415         data->tileWidth = 7;
416         data->tileHeight = 12;
417         break;
418
419     case MAP_MODE_ASCII8x12:
420         data->bAsciiMode = TRUE;
421         data->bFitToScreenMode = FALSE;
422         data->tileWidth = 8;
423         data->tileHeight = 12;
424         break;
425
426     case MAP_MODE_ASCII16x12:
427         data->bAsciiMode = TRUE;
428         data->bFitToScreenMode = FALSE;
429         data->tileWidth = 16;
430         data->tileHeight = 12;
431         break;
432
433     case MAP_MODE_ASCII12x16:
434         data->bAsciiMode = TRUE;
435         data->bFitToScreenMode = FALSE;
436         data->tileWidth = 12;
437         data->tileHeight = 16;
438         break;
439
440     case MAP_MODE_ASCII10x18:
441         data->bAsciiMode = TRUE;
442         data->bFitToScreenMode = FALSE;
443         data->tileWidth = 10;
444         data->tileHeight = 18;
445         break;
446
447     case MAP_MODE_ASCII_FIT_TO_SCREEN:
448         data->bAsciiMode = TRUE;
449         data->bFitToScreenMode = TRUE;
450         data->tileWidth = 12;
451         data->tileHeight = 16;
452         break;
453
454     case MAP_MODE_TILES_FIT_TO_SCREEN:
455         data->bAsciiMode = FALSE;
456         data->bFitToScreenMode = TRUE;
457         data->tileWidth = GetNHApp()->mapTile_X;
458         data->tileHeight = GetNHApp()->mapTile_Y;
459         break;
460
461     case MAP_MODE_TILES:
462     default:
463         data->bAsciiMode = FALSE;
464         data->bFitToScreenMode = FALSE;
465         data->tileWidth = GetNHApp()->mapTile_X;
466         data->tileHeight = GetNHApp()->mapTile_Y;
467         break;
468     }
469
470     mapSize.cx = data->tileWidth * COLNO;
471     mapSize.cy = data->tileHeight * ROWNO;
472
473     mswin_map_stretch(hWnd, &mapSize, TRUE);
474
475     mswin_update_inventory(); /* for perm_invent to hide/show tiles */
476
477     return oldMode;
478 }
479
480 /* register window class for map window */
481 void
482 register_map_window_class()
483 {
484     WNDCLASS wcex;
485     ZeroMemory(&wcex, sizeof(wcex));
486
487     /* window class */
488     wcex.style = CS_NOCLOSE | CS_DBLCLKS;
489     wcex.lpfnWndProc = (WNDPROC) MapWndProc;
490     wcex.cbClsExtra = 0;
491     wcex.cbWndExtra = 0;
492     wcex.hInstance = GetNHApp()->hApp;
493     wcex.hIcon = NULL;
494     wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
495     wcex.hbrBackground =
496         CreateSolidBrush(RGB(0, 0, 0)); /* set backgroup here */
497     wcex.lpszMenuName = NULL;
498     wcex.lpszClassName = szNHMapWindowClass;
499
500     if (!RegisterClass(&wcex)) {
501         panic("cannot register Map window class");
502     }
503 }
504
505 /* map window procedure */
506 LRESULT CALLBACK
507 MapWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
508 {
509     PNHMapWindow data;
510
511     data = (PNHMapWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
512     switch (message) {
513     case WM_CREATE:
514         onCreate(hWnd, wParam, lParam);
515         break;
516
517     case WM_MSNH_COMMAND:
518         onMSNHCommand(hWnd, wParam, lParam);
519         break;
520
521     case WM_PAINT:
522         onPaint(hWnd);
523         break;
524
525     case WM_SETFOCUS:
526         /* transfer focus back to the main window */
527         SetFocus(GetNHApp()->hMainWnd);
528         break;
529
530     case WM_HSCROLL:
531         onMSNH_HScroll(hWnd, wParam, lParam);
532         break;
533
534     case WM_VSCROLL:
535         onMSNH_VScroll(hWnd, wParam, lParam);
536         break;
537
538     case WM_SIZE: {
539         RECT rt;
540         SIZE size;
541
542         if (data->bFitToScreenMode) {
543             size.cx = LOWORD(lParam);
544             size.cy = HIWORD(lParam);
545         } else {
546             /* mapping factor is unchaged we just need to adjust scroll bars
547              */
548             size.cx = data->xFrontTile * COLNO;
549             size.cy = data->yFrontTile * ROWNO;
550         }
551         mswin_map_stretch(hWnd, &size, TRUE);
552
553         /* update window placement */
554         GetWindowRect(hWnd, &rt);
555         ScreenToClient(GetNHApp()->hMainWnd, (LPPOINT) &rt);
556         ScreenToClient(GetNHApp()->hMainWnd, ((LPPOINT) &rt) + 1);
557         mswin_update_window_placement(NHW_MAP, &rt);
558     } break;
559
560     case WM_MOVE: {
561         RECT rt;
562         GetWindowRect(hWnd, &rt);
563         ScreenToClient(GetNHApp()->hMainWnd, (LPPOINT) &rt);
564         ScreenToClient(GetNHApp()->hMainWnd, ((LPPOINT) &rt) + 1);
565         mswin_update_window_placement(NHW_MAP, &rt);
566     }
567         return FALSE;
568
569     case WM_LBUTTONDOWN:
570         NHEVENT_MS(CLICK_1,
571                    max(0, min(COLNO, data->xPos
572                                          + (LOWORD(lParam) - data->map_orig.x)
573                                                / data->xFrontTile)),
574                    max(0, min(ROWNO, data->yPos
575                                          + (HIWORD(lParam) - data->map_orig.y)
576                                                / data->yFrontTile)));
577         return 0;
578
579     case WM_LBUTTONDBLCLK:
580     case WM_RBUTTONDOWN:
581         NHEVENT_MS(CLICK_2,
582                    max(0, min(COLNO, data->xPos
583                                          + (LOWORD(lParam) - data->map_orig.x)
584                                                / data->xFrontTile)),
585                    max(0, min(ROWNO, data->yPos
586                                          + (HIWORD(lParam) - data->map_orig.y)
587                                                / data->yFrontTile)));
588         return 0;
589
590     case WM_DESTROY:
591         if (data->hMapFont)
592             DeleteObject(data->hMapFont);
593         if (data->hBackBuffer)
594             DeleteBitmap(data->hBackBuffer);
595         if (data->backBufferDC)
596             DeleteDC(data->backBufferDC);
597         free(data);
598         SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) 0);
599         break;
600
601     case WM_TIMER:
602         data->cursorOn = !data->cursorOn;
603         dirty(data, data->xCur, data->yCur);
604         break;
605
606     case WM_DPICHANGED: {
607         RECT rt;
608         GetWindowRect(hWnd, &rt);
609         ScreenToClient(GetNHApp()->hMainWnd, (LPPOINT)&rt);
610         ScreenToClient(GetNHApp()->hMainWnd, ((LPPOINT)&rt) + 1);
611         mswin_update_window_placement(NHW_MAP, &rt);
612     } break;
613
614     default:
615         return DefWindowProc(hWnd, message, wParam, lParam);
616     }
617     return 0;
618 }
619
620 /* on WM_COMMAND */
621 void
622 onMSNHCommand(HWND hWnd, WPARAM wParam, LPARAM lParam)
623 {
624     PNHMapWindow data;
625
626     data = (PNHMapWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
627     switch (wParam) {
628     case MSNH_MSG_PRINT_GLYPH: {
629         PMSNHMsgPrintGlyph msg_data = (PMSNHMsgPrintGlyph) lParam;
630         setGlyph(data, msg_data->x, msg_data->y, 
631             msg_data->glyph, msg_data->bkglyph);
632     } break;
633
634     case MSNH_MSG_CLIPAROUND: {
635         PMSNHMsgClipAround msg_data = (PMSNHMsgClipAround) lParam;
636         int x, y;
637         BOOL scroll_x, scroll_y;
638         int mcam = iflags.wc_scroll_margin;
639
640         /* calculate if you should clip around */
641         scroll_x =
642             !GetNHApp()->bNoHScroll
643             && (msg_data->x < (data->xPos + mcam)
644                 || msg_data->x > (data->xPos + data->xPageSize - mcam));
645         scroll_y =
646             !GetNHApp()->bNoVScroll
647             && (msg_data->y < (data->yPos + mcam)
648                 || msg_data->y > (data->yPos + data->yPageSize - mcam));
649
650         mcam += iflags.wc_scroll_amount - 1;
651         /* get page size and center horizontally on x-position */
652         if (scroll_x) {
653             if (data->xPageSize <= 2 * mcam) {
654                 x = max(0, min(COLNO, msg_data->x - data->xPageSize / 2));
655             } else if (msg_data->x < data->xPos + data->xPageSize / 2) {
656                 x = max(0, min(COLNO, msg_data->x - mcam));
657             } else {
658                 x = max(0, min(COLNO, msg_data->x - data->xPageSize + mcam));
659             }
660             SendMessage(hWnd, WM_HSCROLL, (WPARAM) MAKELONG(SB_THUMBTRACK, x),
661                         (LPARAM) NULL);
662         }
663
664         /* get page size and center vertically on y-position */
665         if (scroll_y) {
666             if (data->yPageSize <= 2 * mcam) {
667                 y = max(0, min(ROWNO, msg_data->y - data->yPageSize / 2));
668             } else if (msg_data->y < data->yPos + data->yPageSize / 2) {
669                 y = max(0, min(ROWNO, msg_data->y - mcam));
670             } else {
671                 y = max(0, min(ROWNO, msg_data->y - data->yPageSize + mcam));
672             }
673             SendMessage(hWnd, WM_VSCROLL, (WPARAM) MAKELONG(SB_THUMBTRACK, y),
674                         (LPARAM) NULL);
675         }
676     } break;
677
678     case MSNH_MSG_CLEAR_WINDOW:
679         clearAll(data);
680         break;
681
682     case MSNH_MSG_CURSOR: {
683         PMSNHMsgCursor msg_data = (PMSNHMsgCursor) lParam;
684
685         if (data->xCur != msg_data->x || data->yCur != msg_data->y) {
686
687             dirty(data, data->xCur, data->yCur);
688             dirty(data, msg_data->x, msg_data->y);
689
690             data->xCur = msg_data->x;
691             data->yCur = msg_data->y;
692         }
693  
694     } break;
695
696     case MSNH_MSG_GETTEXT: {
697         PMSNHMsgGetText msg_data = (PMSNHMsgGetText) lParam;
698         size_t index;
699         int col, row;
700         int color;
701         unsigned special;
702         int mgch;
703
704         index = 0;
705         for (row = 0; row < ROWNO; row++) {
706             for (col = 0; col < COLNO; col++) {
707                 if (index >= msg_data->max_size)
708                     break;
709                 if (data->map[col][row] == NO_GLYPH) {
710                     mgch = ' ';
711                 } else {
712                     (void) mapglyph(data->map[col][row], &mgch, &color,
713                                     &special, col, row);
714                 }
715                 msg_data->buffer[index] = mgch;
716                 index++;
717             }
718             if (index >= msg_data->max_size - 1)
719                 break;
720             msg_data->buffer[index++] = '\r';
721             msg_data->buffer[index++] = '\n';
722         }
723     } break;
724
725         case MSNH_MSG_RANDOM_INPUT:
726                 nhassert(0); // unexpected
727                 break;
728
729         } /* end switch(wParam) */
730 }
731
732 /* on WM_CREATE */
733 void
734 onCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
735 {
736     PNHMapWindow data;
737
738     UNREFERENCED_PARAMETER(wParam);
739     UNREFERENCED_PARAMETER(lParam);
740
741     /* set window data */
742     data = (PNHMapWindow) malloc(sizeof(NHMapWindow));
743     if (!data)
744         panic("out of memory");
745
746     ZeroMemory(data, sizeof(NHMapWindow));
747
748     data->hWnd = hWnd;
749
750     data->bAsciiMode = FALSE;
751     data->cursorOn = TRUE;
752
753     data->xFrontTile = GetNHApp()->mapTile_X;
754     data->yFrontTile = GetNHApp()->mapTile_Y;
755     data->tileWidth = GetNHApp()->mapTile_X;
756     data->tileHeight = GetNHApp()->mapTile_Y;
757
758     HDC hDC = GetDC(hWnd);
759     data->backBufferDC = CreateCompatibleDC(hDC);
760     data->tileDC = CreateCompatibleDC(hDC);
761     ReleaseDC(hWnd, hDC);
762
763     SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) data);
764
765     clearAll(data);
766
767 }
768
769 static void
770 paintTile(PNHMapWindow data, int i, int j, RECT * rect)
771 {
772     short ntile;
773     int t_x, t_y;
774     int glyph, bkglyph;
775     int layer;
776 #ifdef USE_PILEMARK
777     int color;
778     unsigned special;
779     int mgch;
780 #endif
781     layer = 0;
782     glyph = data->map[i][j];
783     bkglyph = data->bkmap[i][j];
784
785     if (glyph == NO_GLYPH && bkglyph == NO_GLYPH) {
786         HBRUSH blackBrush = CreateSolidBrush(RGB(0, 0, 0));
787         FillRect(data->backBufferDC, rect, blackBrush);
788         DeleteObject(blackBrush);
789     }
790
791     if (bkglyph != NO_GLYPH) {
792         ntile = glyph2tile[bkglyph];
793         t_x = TILEBMP_X(ntile);
794         t_y = TILEBMP_Y(ntile);
795
796         StretchBlt(data->backBufferDC, rect->left, rect->top,
797                     data->xBackTile, data->yBackTile, data->tileDC,
798                     t_x, t_y, GetNHApp()->mapTile_X,
799                     GetNHApp()->mapTile_Y, SRCCOPY);
800         layer++;
801     }
802
803     if ((glyph != NO_GLYPH) && (glyph != bkglyph)) {
804         ntile = glyph2tile[glyph];
805         t_x = TILEBMP_X(ntile);
806         t_y = TILEBMP_Y(ntile);
807
808         if (layer > 0) {
809             (*GetNHApp()->lpfnTransparentBlt)(
810                 data->backBufferDC, rect->left, rect->top,
811                 data->xBackTile, data->yBackTile, data->tileDC, t_x,
812                 t_y, GetNHApp()->mapTile_X,
813                 GetNHApp()->mapTile_Y, TILE_BK_COLOR);
814         } else {
815             StretchBlt(data->backBufferDC, rect->left, rect->top,
816                         data->xBackTile, data->yBackTile, data->tileDC,
817                         t_x, t_y, GetNHApp()->mapTile_X,
818                         GetNHApp()->mapTile_Y, SRCCOPY);
819         }
820
821         layer++;
822     }
823
824 #ifdef USE_PILEMARK
825     /* rely on NetHack core helper routine */
826     (void) mapglyph(data->map[i][j], &mgch, &color, &special,
827                     i, j);
828     if ((glyph != NO_GLYPH) && (special & MG_PET)
829 #else
830     if ((glyph != NO_GLYPH) && glyph_is_pet(glyph)
831 #endif
832         && iflags.wc_hilite_pet) {
833         /* apply pet mark transparently over
834             pet image */
835         HDC hdcPetMark;
836         HBITMAP bmPetMarkOld;
837
838         /* this is DC for petmark bitmap */
839         hdcPetMark = CreateCompatibleDC(data->backBufferDC);
840         bmPetMarkOld =
841             SelectObject(hdcPetMark, GetNHApp()->bmpPetMark);
842
843         (*GetNHApp()->lpfnTransparentBlt)(
844             data->backBufferDC, rect->left, rect->top,
845             data->xBackTile, data->yBackTile, hdcPetMark, 0, 0,
846             TILE_X, TILE_Y, TILE_BK_COLOR);
847         SelectObject(hdcPetMark, bmPetMarkOld);
848         DeleteDC(hdcPetMark);
849     }
850 #ifdef USE_PILEMARK
851     if ((glyph != NO_GLYPH) && (special & MG_OBJPILE)
852         && iflags.hilite_pile) {
853         /* apply pilemark transparently over other image */
854         HDC hdcPileMark;
855         HBITMAP bmPileMarkOld;
856
857         /* this is DC for pilemark bitmap */
858         hdcPileMark = CreateCompatibleDC(data->backBufferDC);
859         bmPileMarkOld = SelectObject(hdcPileMark,
860                                         GetNHApp()->bmpPileMark);
861
862         (*GetNHApp()->lpfnTransparentBlt)(
863             data->backBufferDC, rect->left, rect->top,
864             data->xBackTile, data->yBackTile, hdcPileMark, 0, 0,
865             TILE_X, TILE_Y, TILE_BK_COLOR);
866         SelectObject(hdcPileMark, bmPileMarkOld);
867         DeleteDC(hdcPileMark);
868     }
869 #endif
870
871     if (i == data->xCur && j == data->yCur && 
872         (data->cursorOn || !win32_cursorblink))
873         DrawFocusRect(data->backBufferDC, rect);
874 }
875
876
877 static void
878 paintGlyph(PNHMapWindow data, int i, int j, RECT * rect)
879 {
880     if (data->map[i][j] >= 0) {
881
882         char ch;
883         WCHAR wch;
884         int color;
885         unsigned special;
886         int mgch;
887         HBRUSH back_brush;
888         COLORREF OldFg;
889
890         SetBkMode(data->backBufferDC, TRANSPARENT);
891
892         HBRUSH blackBrush = CreateSolidBrush(RGB(0, 0, 0));
893         FillRect(data->backBufferDC, rect, blackBrush);
894         DeleteObject(blackBrush);
895
896     #if (VERSION_MAJOR < 4) && (VERSION_MINOR < 4) && (PATCHLEVEL < 2)
897         nhglyph2charcolor(data->map[i][j], &ch, &color);
898         OldFg = SetTextColor(hDC, nhcolor_to_RGB(color));
899     #else
900         /* rely on NetHack core helper routine */
901         (void) mapglyph(data->map[i][j], &mgch, &color,
902                         &special, i, j);
903         ch = (char) mgch;
904         if (((special & MG_PET) && iflags.hilite_pet)
905             || ((special & (MG_DETECT | MG_BW_LAVA))
906                 && iflags.use_inverse)) {
907             back_brush =
908                 CreateSolidBrush(nhcolor_to_RGB(CLR_GRAY));
909             FillRect(data->backBufferDC, rect, back_brush);
910             DeleteObject(back_brush);
911             switch (color) {
912             case CLR_GRAY:
913             case CLR_WHITE:
914                 OldFg = SetTextColor(
915                     data->backBufferDC, nhcolor_to_RGB(CLR_BLACK));
916                 break;
917             default:
918                 OldFg =
919                     SetTextColor(data->backBufferDC, nhcolor_to_RGB(color));
920             }
921         } else {
922             OldFg = SetTextColor(data->backBufferDC, nhcolor_to_RGB(color));
923         }
924     #endif
925         if (data->bUnicodeFont) {
926             wch = winos_ascii_to_wide(ch);
927             if (wch == 0x2591 || wch == 0x2592) {
928                 int level = 80;
929                 HBRUSH brush = CreateSolidBrush(RGB(level, level, level));
930                 FillRect(data->backBufferDC, rect, brush);
931                 DeleteObject(brush);
932                 level = (wch == 0x2591 ? 100 : 200);
933                 brush = CreateSolidBrush(RGB(level, level, level));
934                 RECT smallRect = { rect->left + 1, rect->top + 1,
935                                     rect->right - 1, rect->bottom - 1 };
936                 FillRect(data->backBufferDC, &smallRect, brush);
937                 DeleteObject(brush);
938             } else {
939                 DrawTextW(data->backBufferDC, &wch, 1, rect,
940                     DT_CENTER | DT_VCENTER | DT_NOPREFIX
941                     | DT_SINGLELINE);
942             }
943         } else {
944             DrawTextA(data->backBufferDC, &ch, 1, rect,
945                         DT_CENTER | DT_VCENTER | DT_NOPREFIX
946                             | DT_SINGLELINE);
947         }
948
949         SetTextColor(data->backBufferDC, OldFg);
950     }
951
952     if (i == data->xCur && j == data->yCur &&
953         (data->cursorOn || !win32_cursorblink)) {
954         int yCursor = (win32_cursorblink ? data->yBlinkCursor :
955                                            data->yNoBlinkCursor);
956         PatBlt(data->backBufferDC, 
957                 rect->left, rect->bottom - yCursor,
958                 rect->right - rect->left,
959                 yCursor,
960                 DSTINVERT);
961     }
962 }
963
964 static void setGlyph(PNHMapWindow data, int i, int j, int fg, int bg)
965 {
966     if ((data->map[i][j] != fg) || (data->bkmap[i][j] != bg)) {
967         data->map[i][j] = fg;
968         data->bkmap[i][j] = bg;
969         data->mapDirty[i][j] = TRUE;
970
971         RECT rect;
972         nhcoord2display(data, i, j, &rect);
973         InvalidateRect(data->hWnd, &rect, FALSE);
974     }
975 }
976
977 static void clearAll(PNHMapWindow data)
978 {
979     for (int x = 0; x < COLNO; x++)
980         for (int y = 0; y < ROWNO; y++) {
981             data->map[x][y] = NO_GLYPH;
982             data->bkmap[x][y] = NO_GLYPH;
983             data->mapDirty[x][y] = TRUE;
984         }
985     InvalidateRect(data->hWnd, NULL, FALSE);
986 }
987
988 static void dirtyAll(PNHMapWindow data)
989 {
990     for (int i = 0; i < COLNO; i++)
991         for (int j = 0; j < ROWNO; j++)
992             data->mapDirty[i][j] = TRUE;
993
994     InvalidateRect(data->hWnd, NULL, FALSE);
995 }
996
997 static void dirty(PNHMapWindow data, int x, int y)
998 {
999     data->mapDirty[x][y] = TRUE;
1000
1001     RECT rt;
1002     nhcoord2display(data, data->xCur, data->yCur, &rt);
1003
1004     InvalidateRect(data->hWnd, &rt, FALSE);
1005 }
1006
1007 static void
1008 paint(PNHMapWindow data, int i, int j)
1009 {
1010     RECT rect;
1011
1012     rect.left = i * data->xBackTile;
1013     rect.top = j * data->yBackTile;
1014     rect.right = rect.left + data->xBackTile;
1015     rect.bottom = rect.top + data->yBackTile;
1016
1017     if (data->bAsciiMode || Is_rogue_level(&u.uz)) {
1018         paintGlyph(data, i, j, &rect);
1019     } else {
1020         paintTile(data, i, j, &rect);
1021     }
1022
1023     data->mapDirty[i][j] = FALSE;
1024 }
1025
1026
1027 /* on WM_PAINT */
1028 void
1029 onPaint(HWND hWnd)
1030 {
1031     PNHMapWindow data = (PNHMapWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
1032
1033     /* update back buffer */
1034     HBITMAP savedBitmap = SelectObject(data->tileDC, GetNHApp()->bmpMapTiles);
1035
1036     for (int i = 0; i < COLNO; i++)
1037         for (int j = 0; j < ROWNO; j++)
1038             if (data->mapDirty[i][j])
1039                 paint(data, i, j);
1040
1041     SelectObject(data->tileDC, savedBitmap);
1042
1043     PAINTSTRUCT ps;
1044     HDC hFrontBufferDC = BeginPaint(hWnd, &ps);
1045
1046     /* stretch back buffer onto front buffer window */
1047     int frontWidth = COLNO * data->xFrontTile;
1048     int frontHeight = ROWNO * data->yFrontTile;
1049
1050     StretchBlt(hFrontBufferDC,
1051         data->map_orig.x - (data->xPos * data->xFrontTile),
1052         data->map_orig.y - (data->yPos * data->yFrontTile), frontWidth, frontHeight,
1053                 data->backBufferDC, 0, 0, data->backWidth, data->backHeight, SRCCOPY);
1054
1055     EndPaint(hWnd, &ps);
1056 }
1057
1058 /* on WM_VSCROLL */
1059 void
1060 onMSNH_VScroll(HWND hWnd, WPARAM wParam, LPARAM lParam)
1061 {
1062     PNHMapWindow data;
1063     SCROLLINFO si;
1064     int yNewPos;
1065     int yDelta;
1066
1067     UNREFERENCED_PARAMETER(lParam);
1068
1069     /* get window data */
1070     data = (PNHMapWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
1071
1072     switch (LOWORD(wParam)) {
1073     /* User clicked shaft left of the scroll box. */
1074     case SB_PAGEUP:
1075         yNewPos = data->yPos - data->yPageSize;
1076         break;
1077
1078     /* User clicked shaft right of the scroll box. */
1079     case SB_PAGEDOWN:
1080         yNewPos = data->yPos + data->yPageSize;
1081         break;
1082
1083     /* User clicked the left arrow. */
1084     case SB_LINEUP:
1085         yNewPos = data->yPos - 1;
1086         break;
1087
1088     /* User clicked the right arrow. */
1089     case SB_LINEDOWN:
1090         yNewPos = data->yPos + 1;
1091         break;
1092
1093     /* User dragged the scroll box. */
1094     case SB_THUMBTRACK:
1095         yNewPos = HIWORD(wParam);
1096         break;
1097
1098     default:
1099         yNewPos = data->yPos;
1100     }
1101
1102     yNewPos = max(0, min(data->yMax, yNewPos));
1103     if (yNewPos == data->yPos)
1104         return;
1105
1106     yDelta = yNewPos - data->yPos;
1107     data->yPos = yNewPos;
1108
1109     ScrollWindowEx(hWnd, 0, -data->yFrontTile * yDelta, (CONST RECT *) NULL,
1110                    (CONST RECT *) NULL, (HRGN) NULL, (LPRECT) NULL,
1111                    SW_INVALIDATE | SW_ERASE);
1112
1113     si.cbSize = sizeof(si);
1114     si.fMask = SIF_POS;
1115     si.nPos = data->yPos;
1116     SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
1117 }
1118
1119 /* on WM_HSCROLL */
1120 void
1121 onMSNH_HScroll(HWND hWnd, WPARAM wParam, LPARAM lParam)
1122 {
1123     PNHMapWindow data;
1124     SCROLLINFO si;
1125     int xNewPos;
1126     int xDelta;
1127
1128     UNREFERENCED_PARAMETER(lParam);
1129
1130     /* get window data */
1131     data = (PNHMapWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
1132
1133     switch (LOWORD(wParam)) {
1134     /* User clicked shaft left of the scroll box. */
1135     case SB_PAGEUP:
1136         xNewPos = data->xPos - data->xPageSize;
1137         break;
1138
1139     /* User clicked shaft right of the scroll box. */
1140     case SB_PAGEDOWN:
1141         xNewPos = data->xPos + data->xPageSize;
1142         break;
1143
1144     /* User clicked the left arrow. */
1145     case SB_LINEUP:
1146         xNewPos = data->xPos - 1;
1147         break;
1148
1149     /* User clicked the right arrow. */
1150     case SB_LINEDOWN:
1151         xNewPos = data->xPos + 1;
1152         break;
1153
1154     /* User dragged the scroll box. */
1155     case SB_THUMBTRACK:
1156         xNewPos = HIWORD(wParam);
1157         break;
1158
1159     default:
1160         xNewPos = data->xPos;
1161     }
1162
1163     xNewPos = max(0, min(data->xMax, xNewPos));
1164     if (xNewPos == data->xPos)
1165         return;
1166
1167     xDelta = xNewPos - data->xPos;
1168     data->xPos = xNewPos;
1169
1170     ScrollWindowEx(hWnd, -data->xFrontTile * xDelta, 0, (CONST RECT *) NULL,
1171                    (CONST RECT *) NULL, (HRGN) NULL, (LPRECT) NULL,
1172                    SW_INVALIDATE | SW_ERASE);
1173
1174     si.cbSize = sizeof(si);
1175     si.fMask = SIF_POS;
1176     si.nPos = data->xPos;
1177     SetScrollInfo(hWnd, SB_HORZ, &si, TRUE);
1178 }
1179
1180 /* map nethack map coordinates to the screen location */
1181 void
1182 nhcoord2display(PNHMapWindow data, int x, int y, LPRECT lpOut)
1183 {
1184     lpOut->left = (x - data->xPos) * data->xFrontTile + data->map_orig.x;
1185     lpOut->top = (y - data->yPos) * data->yFrontTile + data->map_orig.y;
1186     lpOut->right = lpOut->left + data->xFrontTile;
1187     lpOut->bottom = lpOut->top + data->yFrontTile;
1188 }
1189
1190 #if (VERSION_MAJOR < 4) && (VERSION_MINOR < 4) && (PATCHLEVEL < 2)
1191 /* map glyph to character/color combination */
1192 void
1193 nhglyph2charcolor(short g, uchar *ch, int *color)
1194 {
1195     int offset;
1196 #ifdef TEXTCOLOR
1197
1198 #define zap_color(n) *color = iflags.use_color ? zapcolors[n] : NO_COLOR
1199 #define cmap_color(n) *color = iflags.use_color ? defsyms[n].color : NO_COLOR
1200 #define obj_color(n) \
1201     *color = iflags.use_color ? objects[n].oc_color : NO_COLOR
1202 #define mon_color(n) *color = iflags.use_color ? mons[n].mcolor : NO_COLOR
1203 #define pet_color(n) *color = iflags.use_color ? mons[n].mcolor : NO_COLOR
1204 #define warn_color(n) \
1205     *color = iflags.use_color ? def_warnsyms[n].color : NO_COLOR
1206
1207 #else /* no text color */
1208
1209 #define zap_color(n)
1210 #define cmap_color(n)
1211 #define obj_color(n)
1212 #define mon_color(n)
1213 #define pet_color(c)
1214 #define warn_color(c)
1215     *color = CLR_WHITE;
1216 #endif
1217
1218     if ((offset = (g - GLYPH_WARNING_OFF)) >= 0) { /* a warning flash */
1219         *ch = showsyms[offset + SYM_OFF_W];
1220         warn_color(offset);
1221     } else if ((offset = (g - GLYPH_SWALLOW_OFF)) >= 0) { /* swallow */
1222         /* see swallow_to_glyph() in display.c */
1223         *ch = (uchar) showsyms[(S_sw_tl + (offset & 0x7)) + SYM_OFF_P];
1224         mon_color(offset >> 3);
1225     } else if ((offset = (g - GLYPH_ZAP_OFF)) >= 0) { /* zap beam */
1226         /* see zapdir_to_glyph() in display.c */
1227         *ch = showsyms[(S_vbeam + (offset & 0x3)) + SYM_OFF_P];
1228         zap_color((offset >> 2));
1229     } else if ((offset = (g - GLYPH_CMAP_OFF)) >= 0) { /* cmap */
1230         *ch = showsyms[offset + SYM_OFF_P];
1231         cmap_color(offset);
1232     } else if ((offset = (g - GLYPH_OBJ_OFF)) >= 0) { /* object */
1233         *ch = showsyms[(int) objects[offset].oc_class + SYM_OFF_O];
1234         obj_color(offset);
1235     } else if ((offset = (g - GLYPH_BODY_OFF)) >= 0) { /* a corpse */
1236         *ch = showsyms[(int) objects[CORPSE].oc_class + SYM_OFF_O];
1237         mon_color(offset);
1238     } else if ((offset = (g - GLYPH_PET_OFF)) >= 0) { /* a pet */
1239         *ch = showsyms[(int) mons[offset].mlet + SYM_OFF_M];
1240         pet_color(offset);
1241     } else { /* a monster */
1242         *ch = showsyms[(int) mons[g].mlet + SYM_OFF_M];
1243         mon_color(g);
1244     }
1245     // end of wintty code
1246 }
1247 #endif
1248
1249 /* map nethack color to RGB */
1250 COLORREF
1251 nhcolor_to_RGB(int c)
1252 {
1253     if (c >= 0 && c < CLR_MAX)
1254         return GetNHApp()->regMapColors[c];
1255     return RGB(0x00, 0x00, 0x00);
1256 }