OSDN Git Service

upgrade to 3.6.2
[jnethack/source.git] / win / win32 / mhmenu.c
1 /* NetHack 3.6  mhmenu.c        $NHDT-Date: 1432512811 2015/05/25 00:13:31 $  $NHDT-Branch: master $:$NHDT-Revision: 1.48 $ */
2 /* Copyright (c) Alex Kompel, 2002                                */
3 /* NetHack may be freely redistributed.  See license for details. */
4
5 #include "win10.h"
6 #include "winMS.h"
7 #include <assert.h>
8 #include "resource.h"
9 #include "mhmenu.h"
10 #include "mhmain.h"
11 #include "mhmsg.h"
12 #include "mhfont.h"
13 #include "mhdlg.h"
14
15 #define MENU_MARGIN 0
16 #define NHMENU_STR_SIZE BUFSZ
17 #define MIN_TABSTOP_SIZE 0
18 #define NUMTABS 15
19 #define TAB_SEPARATION 10 /* pixels between each tab stop */
20
21 #define DEFAULT_COLOR_BG_TEXT COLOR_WINDOW
22 #define DEFAULT_COLOR_FG_TEXT COLOR_WINDOWTEXT
23 #define DEFAULT_COLOR_BG_MENU COLOR_WINDOW
24 #define DEFAULT_COLOR_FG_MENU COLOR_WINDOWTEXT
25
26 #define CHECK_WIDTH 16
27 #define CHECK_HEIGHT 16
28
29 typedef struct mswin_menu_item {
30     int glyph;
31     ANY_P identifier;
32     CHAR_P accelerator;
33     CHAR_P group_accel;
34     int attr;
35     char str[NHMENU_STR_SIZE];
36     BOOLEAN_P presel;
37     int count;
38     BOOL has_focus;
39 } NHMenuItem, *PNHMenuItem;
40
41 typedef struct mswin_nethack_menu_window {
42     int type; /* MENU_TYPE_TEXT or MENU_TYPE_MENU */
43     int how;  /* for menus: PICK_NONE, PICK_ONE, PICK_ANY */
44
45     union {
46         struct menu_list {
47             int size;            /* number of items in items[] */
48             int allocated;       /* number of allocated slots in items[] */
49             PNHMenuItem items;   /* menu items */
50             char gacc[QBUFSZ];   /* group accelerators */
51             BOOL counting;       /* counting flag */
52             char prompt[QBUFSZ]; /* menu prompt */
53             int tab_stop_size[NUMTABS]; /* tabstops to align option values */
54             int menu_cx;                /* menu width */
55         } menu;
56
57         struct menu_text {
58             TCHAR *text;
59             SIZE text_box_size;
60         } text;
61     };
62     int result;
63     int done;
64
65     HBITMAP bmpChecked;
66     HBITMAP bmpCheckedCount;
67     HBITMAP bmpNotChecked;
68     HDC bmpDC;
69
70     BOOL is_active;
71 } NHMenuWindow, *PNHMenuWindow;
72
73 extern short glyph2tile[];
74
75 static WNDPROC wndProcListViewOrig = NULL;
76 static WNDPROC editControlWndProc = NULL;
77
78 #define NHMENU_IS_SELECTABLE(item) ((item).identifier.a_obj != NULL)
79 #define NHMENU_IS_SELECTED(item) ((item).count != 0)
80 #define NHMENU_HAS_GLYPH(item) ((item).glyph != NO_GLYPH)
81
82 INT_PTR CALLBACK MenuWndProc(HWND, UINT, WPARAM, LPARAM);
83 LRESULT CALLBACK NHMenuListWndProc(HWND, UINT, WPARAM, LPARAM);
84 LRESULT CALLBACK NHMenuTextWndProc(HWND, UINT, WPARAM, LPARAM);
85 static void onMSNHCommand(HWND hWnd, WPARAM wParam, LPARAM lParam);
86 static BOOL onMeasureItem(HWND hWnd, WPARAM wParam, LPARAM lParam);
87 static BOOL onDrawItem(HWND hWnd, WPARAM wParam, LPARAM lParam);
88 static void LayoutMenu(HWND hwnd);
89 static void SetMenuType(HWND hwnd, int type);
90 static void SetMenuListType(HWND hwnd, int now);
91 static HWND GetMenuControl(HWND hwnd);
92 static void SelectMenuItem(HWND hwndList, PNHMenuWindow data, int item,
93                            int count);
94 static void reset_menu_count(HWND hwndList, PNHMenuWindow data);
95 static BOOL onListChar(HWND hWnd, HWND hwndList, WORD ch);
96
97 /*-----------------------------------------------------------------------------*/
98 HWND
99 mswin_init_menu_window(int type)
100 {
101     HWND ret;
102     RECT rt;
103
104     /* get window position */
105     if (GetNHApp()->bAutoLayout) {
106         SetRect(&rt, 0, 0, 0, 0);
107     } else {
108         mswin_get_window_placement(NHW_MENU, &rt);
109     }
110
111     /* create menu window object */
112     ret = CreateDialog(GetNHApp()->hApp, MAKEINTRESOURCE(IDD_MENU),
113                        GetNHApp()->hMainWnd, MenuWndProc);
114     if (!ret) {
115         panic("Cannot create menu window");
116     }
117
118     /* move it in the predefined position */
119     if (!GetNHApp()->bAutoLayout) {
120         MoveWindow(ret, rt.left, rt.top, rt.right - rt.left,
121                    rt.bottom - rt.top, TRUE);
122     }
123
124     /* Set window caption */
125     SetWindowText(ret, "Menu/Text");
126
127     mswin_apply_window_style(ret);
128
129     SetMenuType(ret, type);
130     return ret;
131 }
132 /*-----------------------------------------------------------------------------*/
133 int
134 mswin_menu_window_select_menu(HWND hWnd, int how, MENU_ITEM_P **_selected,
135                               BOOL activate)
136 {
137     PNHMenuWindow data;
138     int ret_val;
139     MENU_ITEM_P *selected = NULL;
140     int i;
141     char *ap;
142
143     assert(_selected != NULL);
144     *_selected = NULL;
145     ret_val = -1;
146
147     data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
148
149     /* force activate for certain menu types */
150     if (data->type == MENU_TYPE_MENU
151         && (how == PICK_ONE || how == PICK_ANY)) {
152         activate = TRUE;
153     }
154
155     data->is_active = activate && !GetNHApp()->regNetHackMode;
156
157     /* set menu type */
158     SetMenuListType(hWnd, how);
159
160     /* Ok, now give items a unique accelerators */
161     if (data->type == MENU_TYPE_MENU) {
162         char next_char = 'a';
163
164         data->menu.gacc[0] = '\0';
165         ap = data->menu.gacc;
166         for (i = 0; i < data->menu.size; i++) {
167             if (data->menu.items[i].accelerator != 0) {
168                 if (isalpha(data->menu.items[i].accelerator)) {
169                     next_char = (char)(data->menu.items[i].accelerator + 1);
170                 }
171             } else if (NHMENU_IS_SELECTABLE(data->menu.items[i])) {
172                 if (isalpha(next_char)) {
173                     data->menu.items[i].accelerator = next_char;
174                 } else {
175                     if (next_char > 'z')
176                         next_char = 'A';
177                     else if (next_char > 'Z')
178                         next_char = 'a';
179
180                     data->menu.items[i].accelerator = next_char;
181                 }
182
183                 next_char++;
184             }
185         }
186
187         /* collect group accelerators */
188         for (i = 0; i < data->menu.size; i++) {
189             if (data->how != PICK_NONE) {
190                 if (data->menu.items[i].group_accel
191                     && !strchr(data->menu.gacc,
192                                data->menu.items[i].group_accel)) {
193                     *ap++ = data->menu.items[i].group_accel;
194                     *ap = '\x0';
195                 }
196             }
197         }
198
199         reset_menu_count(NULL, data);
200     }
201
202     LayoutMenu(hWnd); // show dialog buttons
203
204     if (activate) {
205         mswin_popup_display(hWnd, &data->done);
206     } else {
207         SetFocus(GetNHApp()->hMainWnd);
208         mswin_layout_main_window(hWnd);
209     }
210
211     /* get the result */
212     if (data->result != -1) {
213         if (how == PICK_NONE) {
214             if (data->result >= 0)
215                 ret_val = 0;
216             else
217                 ret_val = -1;
218         } else if (how == PICK_ONE || how == PICK_ANY) {
219             /* count selected items */
220             ret_val = 0;
221             for (i = 0; i < data->menu.size; i++) {
222                 if (NHMENU_IS_SELECTABLE(data->menu.items[i])
223                     && NHMENU_IS_SELECTED(data->menu.items[i])) {
224                     ret_val++;
225                 }
226             }
227             if (ret_val > 0) {
228                 int sel_ind;
229
230                 selected =
231                     (MENU_ITEM_P *) malloc(ret_val * sizeof(MENU_ITEM_P));
232                 if (!selected)
233                     panic("out of memory");
234
235                 sel_ind = 0;
236                 for (i = 0; i < data->menu.size; i++) {
237                     if (NHMENU_IS_SELECTABLE(data->menu.items[i])
238                         && NHMENU_IS_SELECTED(data->menu.items[i])) {
239                         selected[sel_ind].item =
240                             data->menu.items[i].identifier;
241                         selected[sel_ind].count = data->menu.items[i].count;
242                         sel_ind++;
243                     }
244                 }
245                 ret_val = sel_ind;
246                 *_selected = selected;
247             }
248         }
249     }
250
251     if (activate) {
252         data->is_active = FALSE;
253         LayoutMenu(hWnd); // hide dialog buttons
254         mswin_popup_destroy(hWnd);
255
256         /* If we just used the permanent inventory window to pick something,
257          * set the menu back to its display inventory state.
258          */
259         if (iflags.perm_invent && mswin_winid_from_handle(hWnd) == WIN_INVEN
260             && how != PICK_NONE) {
261             data->menu.prompt[0] = '\0';
262             SetMenuListType(hWnd, PICK_NONE);
263             for (i = 0; i < data->menu.size; i++)
264                 data->menu.items[i].count = 0;
265             LayoutMenu(hWnd);
266         }
267     }
268
269     return ret_val;
270 }
271 /*-----------------------------------------------------------------------------*/
272 INT_PTR CALLBACK
273 MenuWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
274 {
275     PNHMenuWindow data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
276     HWND control = GetDlgItem(hWnd, IDC_MENU_TEXT);
277     TCHAR title[MAX_LOADSTRING];
278
279     switch (message) {
280     case WM_INITDIALOG: {
281
282         HDC hdc = GetDC(control);
283         data = (PNHMenuWindow) malloc(sizeof(NHMenuWindow));
284         ZeroMemory(data, sizeof(NHMenuWindow));
285         data->type = MENU_TYPE_TEXT;
286         data->how = PICK_NONE;
287         data->result = 0;
288         data->done = 0;
289         data->bmpChecked =
290             LoadBitmap(GetNHApp()->hApp, MAKEINTRESOURCE(IDB_MENU_SEL));
291         data->bmpCheckedCount =
292             LoadBitmap(GetNHApp()->hApp, MAKEINTRESOURCE(IDB_MENU_SEL_COUNT));
293         data->bmpNotChecked =
294             LoadBitmap(GetNHApp()->hApp, MAKEINTRESOURCE(IDB_MENU_UNSEL));
295         data->bmpDC = CreateCompatibleDC(hdc);
296         data->is_active = FALSE;
297         SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) data);
298
299         /* set font for the text cotrol */
300         cached_font * font = mswin_get_font(NHW_MENU, ATR_NONE, hdc, FALSE);
301         SendMessage(control, WM_SETFONT,
302                     (WPARAM) font->hFont,
303                     (LPARAM) 0);
304         ReleaseDC(control, hdc);
305
306         /* subclass edit control */
307         editControlWndProc =
308             (WNDPROC) GetWindowLongPtr(control, GWLP_WNDPROC);
309         SetWindowLongPtr(control, GWLP_WNDPROC, (LONG_PTR) NHMenuTextWndProc);
310
311         /* Even though the dialog has no caption, you can still set the title
312            which shows on Alt-Tab */
313         LoadString(GetNHApp()->hApp, IDS_APP_TITLE, title, MAX_LOADSTRING);
314         SetWindowText(hWnd, title);
315
316         /* set focus to text control for now */
317         SetFocus(control);
318     }
319         return FALSE;
320
321     case WM_MSNH_COMMAND:
322         onMSNHCommand(hWnd, wParam, lParam);
323         break;
324
325     case WM_SIZE: {
326         RECT rt;
327         LayoutMenu(hWnd);
328         GetWindowRect(hWnd, &rt);
329         ScreenToClient(GetNHApp()->hMainWnd, (LPPOINT) &rt);
330         ScreenToClient(GetNHApp()->hMainWnd, ((LPPOINT) &rt) + 1);
331         if (iflags.perm_invent && mswin_winid_from_handle(hWnd) == WIN_INVEN)
332             mswin_update_window_placement(NHW_INVEN, &rt);
333         else
334             mswin_update_window_placement(NHW_MENU, &rt);
335     }
336         return FALSE;
337
338     case WM_MOVE: {
339         RECT rt;
340         GetWindowRect(hWnd, &rt);
341         ScreenToClient(GetNHApp()->hMainWnd, (LPPOINT) &rt);
342         ScreenToClient(GetNHApp()->hMainWnd, ((LPPOINT) &rt) + 1);
343         if (iflags.perm_invent && mswin_winid_from_handle(hWnd) == WIN_INVEN)
344             mswin_update_window_placement(NHW_INVEN, &rt);
345         else
346             mswin_update_window_placement(NHW_MENU, &rt);
347     }
348         return FALSE;
349
350     case WM_CLOSE:
351         if (program_state.gameover) {
352             data->result = -1;
353             data->done = 1;
354             program_state.stopprint++;
355             return TRUE;
356         } else
357             return FALSE;
358
359     case WM_COMMAND: {
360         switch (LOWORD(wParam)) {
361         case IDCANCEL:
362             if (data->type == MENU_TYPE_MENU
363                 && (data->how == PICK_ONE || data->how == PICK_ANY)
364                 && data->menu.counting) {
365                 HWND list;
366                 int i;
367
368                 /* reset counter if counting is in progress */
369                 list = GetMenuControl(hWnd);
370                 i = ListView_GetNextItem(list, -1, LVNI_FOCUSED);
371                 if (i >= 0) {
372                     SelectMenuItem(list, data, i, 0);
373                 }
374                 return TRUE;
375             } else {
376                 data->result = -1;
377                 data->done = 1;
378             }
379             return TRUE;
380
381         case IDOK:
382             data->done = 1;
383             data->result = 0;
384             return TRUE;
385         }
386     } break;
387
388     case WM_NOTIFY: {
389         LPNMHDR lpnmhdr = (LPNMHDR) lParam;
390         switch (LOWORD(wParam)) {
391         case IDC_MENU_LIST: {
392             if (!data || data->type != MENU_TYPE_MENU)
393                 break;
394
395             switch (lpnmhdr->code) {
396             case LVN_ITEMACTIVATE: {
397                 LPNMLISTVIEW lpnmlv = (LPNMLISTVIEW) lParam;
398                 if (data->how == PICK_ONE) {
399                     if (lpnmlv->iItem >= 0 && lpnmlv->iItem < data->menu.size
400                         && NHMENU_IS_SELECTABLE(
401                                data->menu.items[lpnmlv->iItem])) {
402                         SelectMenuItem(lpnmlv->hdr.hwndFrom, data,
403                                        lpnmlv->iItem, -1);
404                         data->done = 1;
405                         data->result = 0;
406                         return TRUE;
407                     }
408                 }
409             } break;
410
411             case NM_CLICK: {
412                 LPNMLISTVIEW lpnmitem = (LPNMLISTVIEW) lParam;
413                 if (lpnmitem->iItem == -1)
414                     return 0;
415                 if (data->how == PICK_ANY) {
416                     SelectMenuItem(
417                         lpnmitem->hdr.hwndFrom, data, lpnmitem->iItem,
418                         NHMENU_IS_SELECTED(data->menu.items[lpnmitem->iItem])
419                             ? 0
420                             : -1);
421                 }
422             } break;
423
424             case LVN_ITEMCHANGED: {
425                 LPNMLISTVIEW lpnmlv = (LPNMLISTVIEW) lParam;
426                 if (lpnmlv->iItem == -1)
427                     return 0;
428                 if (!(lpnmlv->uChanged & LVIF_STATE))
429                     return 0;
430
431                 if (data->how == PICK_ONE || data->how == PICK_ANY) {
432                     data->menu.items[lpnmlv->iItem].has_focus =
433                         !!(lpnmlv->uNewState & LVIS_FOCUSED);
434                     ListView_RedrawItems(lpnmlv->hdr.hwndFrom, lpnmlv->iItem,
435                                          lpnmlv->iItem);
436                 }
437
438                 /* update count for single-selection menu (follow the listview
439                  * selection) */
440                 if (data->how == PICK_ONE) {
441                     if (lpnmlv->uNewState & LVIS_SELECTED) {
442                         SelectMenuItem(lpnmlv->hdr.hwndFrom, data,
443                                        lpnmlv->iItem, -1);
444                     }
445                 }
446
447                 /* check item focus */
448                 if (data->how == PICK_ONE || data->how == PICK_ANY) {
449                     data->menu.items[lpnmlv->iItem].has_focus =
450                         !!(lpnmlv->uNewState & LVIS_FOCUSED);
451                     ListView_RedrawItems(lpnmlv->hdr.hwndFrom, lpnmlv->iItem,
452                                          lpnmlv->iItem);
453                 }
454             } break;
455
456             case NM_KILLFOCUS:
457                 reset_menu_count(lpnmhdr->hwndFrom, data);
458                 break;
459             }
460         } break;
461         }
462     } break;
463
464     case WM_SETFOCUS:
465         if (hWnd != GetNHApp()->hPopupWnd) {
466             SetFocus(GetNHApp()->hMainWnd);
467         } else {
468             if (IsWindow(GetMenuControl(hWnd)))
469                 SetFocus(GetMenuControl(hWnd));
470         }
471         return FALSE;
472
473     case WM_MEASUREITEM:
474         if (wParam == IDC_MENU_LIST)
475             return onMeasureItem(hWnd, wParam, lParam);
476         else
477             return FALSE;
478
479     case WM_DRAWITEM:
480         if (wParam == IDC_MENU_LIST)
481             return onDrawItem(hWnd, wParam, lParam);
482         else
483             return FALSE;
484
485     case WM_CTLCOLORSTATIC: { /* sent by edit control before it is drawn */
486         HDC hdcEdit = (HDC) wParam;
487         HWND hwndEdit = (HWND) lParam;
488         if (hwndEdit == GetDlgItem(hWnd, IDC_MENU_TEXT)) {
489             SetBkColor(hdcEdit, text_bg_brush ? text_bg_color
490                                               : (COLORREF) GetSysColor(
491                                                     DEFAULT_COLOR_BG_TEXT));
492             SetTextColor(hdcEdit, text_fg_brush ? text_fg_color
493                                                 : (COLORREF) GetSysColor(
494                                                       DEFAULT_COLOR_FG_TEXT));
495             return (INT_PTR)(text_bg_brush
496                                  ? text_bg_brush
497                                  : SYSCLR_TO_BRUSH(DEFAULT_COLOR_BG_TEXT));
498         }
499     }
500     return FALSE;
501
502     case WM_CTLCOLORDLG:
503         return (INT_PTR)(text_bg_brush
504                             ? text_bg_brush
505                             : SYSCLR_TO_BRUSH(DEFAULT_COLOR_BG_TEXT));
506
507     case WM_DESTROY:
508         if (data) {
509             DeleteDC(data->bmpDC);
510             DeleteObject(data->bmpChecked);
511             DeleteObject(data->bmpCheckedCount);
512             DeleteObject(data->bmpNotChecked);
513             if (data->type == MENU_TYPE_TEXT) {
514                 if (data->text.text)
515                     free(data->text.text);
516             }
517             free(data);
518             SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) 0);
519         }
520         return TRUE;
521     }
522     return FALSE;
523 }
524 /*-----------------------------------------------------------------------------*/
525 void
526 onMSNHCommand(HWND hWnd, WPARAM wParam, LPARAM lParam)
527 {
528     PNHMenuWindow data;
529
530     data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
531     switch (wParam) {
532     case MSNH_MSG_PUTSTR: {
533         PMSNHMsgPutstr msg_data = (PMSNHMsgPutstr) lParam;
534         HWND text_view;
535         TCHAR wbuf[BUFSZ];
536         size_t text_size;
537         RECT text_rt;
538         HGDIOBJ saveFont;
539         HDC hdc;
540
541         if (data->type != MENU_TYPE_TEXT)
542             SetMenuType(hWnd, MENU_TYPE_TEXT);
543
544         if (!data->text.text) {
545             text_size = strlen(msg_data->text) + 4;
546             data->text.text =
547                 (TCHAR *) malloc(text_size * sizeof(data->text.text[0]));
548             ZeroMemory(data->text.text,
549                        text_size * sizeof(data->text.text[0]));
550         } else {
551             text_size = _tcslen(data->text.text) + strlen(msg_data->text) + 4;
552             data->text.text = (TCHAR *) realloc(
553                 data->text.text, text_size * sizeof(data->text.text[0]));
554         }
555         if (!data->text.text)
556             break;
557
558         _tcscat(data->text.text, NH_A2W(msg_data->text, wbuf, BUFSZ));
559         _tcscat(data->text.text, TEXT("\r\n"));
560
561         text_view = GetDlgItem(hWnd, IDC_MENU_TEXT);
562         if (!text_view)
563             panic("cannot get text view window");
564         SetWindowText(text_view, data->text.text);
565
566         /* calculate dimensions of the added line of text */
567         hdc = GetDC(text_view);
568         cached_font * font = mswin_get_font(NHW_MENU, ATR_NONE, hdc, FALSE);
569         saveFont = SelectObject(hdc, font->hFont);
570         SetRect(&text_rt, 0, 0, 0, 0);
571         DrawTextA(hdc, msg_data->text, strlen(msg_data->text), &text_rt,
572                  DT_CALCRECT | DT_TOP | DT_LEFT | DT_NOPREFIX
573                      | DT_SINGLELINE);
574         data->text.text_box_size.cx =
575             max(text_rt.right - text_rt.left, data->text.text_box_size.cx);
576         data->text.text_box_size.cy += text_rt.bottom - text_rt.top;
577         SelectObject(hdc, saveFont);
578         ReleaseDC(text_view, hdc);
579     } break;
580
581     case MSNH_MSG_STARTMENU: {
582         int i;
583         if (data->type != MENU_TYPE_MENU)
584             SetMenuType(hWnd, MENU_TYPE_MENU);
585
586         if (data->menu.items)
587             free(data->menu.items);
588         data->how = PICK_NONE;
589         data->menu.items = NULL;
590         data->menu.size = 0;
591         data->menu.allocated = 0;
592         data->done = 0;
593         data->result = 0;
594         for (i = 0; i < NUMTABS; ++i)
595             data->menu.tab_stop_size[i] = MIN_TABSTOP_SIZE;
596     } break;
597
598     case MSNH_MSG_ADDMENU: {
599         PMSNHMsgAddMenu msg_data = (PMSNHMsgAddMenu) lParam;
600         char *p, *p1;
601         int new_item;
602         HDC hDC;
603         int column;
604         HFONT saveFont;
605         LONG menuitemwidth = 0;
606         TEXTMETRIC tm;
607
608         if (data->type != MENU_TYPE_MENU)
609             break;
610
611         if (data->menu.size == data->menu.allocated) {
612             data->menu.allocated += 10;
613             data->menu.items = (PNHMenuItem) realloc(
614                 data->menu.items, data->menu.allocated * sizeof(NHMenuItem));
615         }
616
617         new_item = data->menu.size;
618         ZeroMemory(&data->menu.items[new_item],
619                    sizeof(data->menu.items[new_item]));
620         data->menu.items[new_item].glyph = msg_data->glyph;
621         data->menu.items[new_item].identifier = *msg_data->identifier;
622         data->menu.items[new_item].accelerator = msg_data->accelerator;
623         data->menu.items[new_item].group_accel = msg_data->group_accel;
624         data->menu.items[new_item].attr = msg_data->attr;
625         strncpy(data->menu.items[new_item].str, msg_data->str,
626                 NHMENU_STR_SIZE);
627         /* prevent & being interpreted as a mnemonic start */
628         strNsubst(data->menu.items[new_item].str, "&", "&&", 0);
629         data->menu.items[new_item].presel = msg_data->presel;
630
631         /* calculate tabstop size */
632         hDC = GetDC(hWnd);
633         cached_font * font = mswin_get_font(NHW_MENU, msg_data->attr, hDC, FALSE);
634         saveFont = SelectObject(hDC, font->hFont);
635         GetTextMetrics(hDC, &tm);
636         p1 = data->menu.items[new_item].str;
637         p = strchr(data->menu.items[new_item].str, '\t');
638         column = 0;
639         for (;;) {
640             TCHAR wbuf[BUFSZ];
641             RECT drawRect;
642             SetRect(&drawRect, 0, 0, 1, 1);
643             if (p != NULL)
644                 *p = '\0'; /* for time being, view tab field as zstring */
645             DrawText(hDC, NH_A2W(p1, wbuf, BUFSZ), strlen(p1), &drawRect,
646                      DT_CALCRECT | DT_LEFT | DT_VCENTER | DT_EXPANDTABS
647                          | DT_SINGLELINE);
648             data->menu.tab_stop_size[column] =
649                 max(data->menu.tab_stop_size[column],
650                     drawRect.right - drawRect.left);
651
652             menuitemwidth += data->menu.tab_stop_size[column];
653
654             if (p != NULL)
655                 *p = '\t';
656             else /* last string so, */
657                 break;
658
659             /* add the separation only when not the last item */
660             /* in the last item, we break out of the loop, in the statement
661              * just above */
662             menuitemwidth += TAB_SEPARATION;
663
664             ++column;
665             p1 = p + 1;
666             p = strchr(p1, '\t');
667         }
668         SelectObject(hDC, saveFont);
669         ReleaseDC(hWnd, hDC);
670
671         /* calculate new menu width */
672         data->menu.menu_cx =
673             max(data->menu.menu_cx,
674                 2 * TILE_X + menuitemwidth
675                     + (tm.tmAveCharWidth + tm.tmOverhang) * 12);
676
677         /* increment size */
678         data->menu.size++;
679     } break;
680
681     case MSNH_MSG_ENDMENU: {
682         PMSNHMsgEndMenu msg_data = (PMSNHMsgEndMenu) lParam;
683         if (msg_data->text) {
684             strncpy(data->menu.prompt, msg_data->text,
685                     sizeof(data->menu.prompt) - 1);
686         } else {
687             ZeroMemory(data->menu.prompt, sizeof(data->menu.prompt));
688         }
689     } break;
690
691         case MSNH_MSG_RANDOM_INPUT: {
692         PostMessage(GetMenuControl(hWnd),
693             WM_MSNH_COMMAND, MSNH_MSG_RANDOM_INPUT, 0);
694         } break;
695
696     }
697 }
698 /*-----------------------------------------------------------------------------*/
699 void
700 LayoutMenu(HWND hWnd)
701 {
702     PNHMenuWindow data;
703     HWND menu_ok;
704     HWND menu_cancel;
705     RECT clrt, rt;
706     POINT pt_elem, pt_ok, pt_cancel;
707     SIZE sz_elem, sz_ok, sz_cancel;
708
709     data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
710     menu_ok = GetDlgItem(hWnd, IDOK);
711     menu_cancel = GetDlgItem(hWnd, IDCANCEL);
712
713     /* get window coordinates */
714     GetClientRect(hWnd, &clrt);
715
716     // OK button
717     if (data->is_active) {
718         GetWindowRect(menu_ok, &rt);
719         if (data->type == MENU_TYPE_TEXT
720             || (data->type == MENU_TYPE_MENU && data->how == PICK_NONE)) {
721             sz_ok.cx = (clrt.right - clrt.left) - 2 * MENU_MARGIN;
722         } else {
723             sz_ok.cx = (clrt.right - clrt.left) / 2 - 2 * MENU_MARGIN;
724         }
725         sz_ok.cy = rt.bottom - rt.top;
726         pt_ok.x = clrt.left + MENU_MARGIN;
727         pt_ok.y = clrt.bottom - MENU_MARGIN - sz_ok.cy;
728         ShowWindow(menu_ok, SW_SHOW);
729         MoveWindow(menu_ok, pt_ok.x, pt_ok.y, sz_ok.cx, sz_ok.cy, TRUE);
730     } else {
731         sz_ok.cx = sz_ok.cy = 0;
732         pt_ok.x = pt_ok.y = 0;
733         ShowWindow(menu_ok, SW_HIDE);
734     }
735
736     // CANCEL button
737     if (data->is_active
738         && !(data->type == MENU_TYPE_TEXT
739              || (data->type == MENU_TYPE_MENU && data->how == PICK_NONE))) {
740         GetWindowRect(menu_ok, &rt);
741         sz_cancel.cx = (clrt.right - clrt.left) / 2 - 2 * MENU_MARGIN;
742         pt_cancel.x = (clrt.left + clrt.right) / 2 + MENU_MARGIN;
743         sz_cancel.cy = rt.bottom - rt.top;
744         pt_cancel.y = clrt.bottom - MENU_MARGIN - sz_cancel.cy;
745         ShowWindow(menu_cancel, SW_SHOW);
746         MoveWindow(menu_cancel, pt_cancel.x, pt_cancel.y, sz_cancel.cx,
747                    sz_cancel.cy, TRUE);
748     } else {
749         sz_cancel.cx = sz_cancel.cy = 0;
750         pt_cancel.x = pt_cancel.y = 0;
751         ShowWindow(menu_cancel, SW_HIDE);
752     }
753
754     // main menu control
755     pt_elem.x = clrt.left + MENU_MARGIN;
756     pt_elem.y = clrt.top + MENU_MARGIN;
757     sz_elem.cx = (clrt.right - clrt.left) - 2 * MENU_MARGIN;
758     if (data->is_active) {
759         sz_elem.cy = (clrt.bottom - clrt.top) - max(sz_ok.cy, sz_cancel.cy)
760                      - 3 * MENU_MARGIN;
761     } else {
762         sz_elem.cy = (clrt.bottom - clrt.top) - 2 * MENU_MARGIN;
763     }
764
765     if (data->type == MENU_TYPE_MENU) {
766         ListView_SetColumnWidth(
767             GetMenuControl(hWnd), 0,
768             max(clrt.right - clrt.left - GetSystemMetrics(SM_CXVSCROLL),
769                 data->menu.menu_cx));
770     }
771
772     MoveWindow(GetMenuControl(hWnd), pt_elem.x, pt_elem.y, sz_elem.cx,
773                sz_elem.cy, TRUE);
774 }
775 /*-----------------------------------------------------------------------------*/
776 void
777 SetMenuType(HWND hWnd, int type)
778 {
779     PNHMenuWindow data;
780     HWND list, text;
781
782     data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
783
784     data->type = type;
785
786     text = GetDlgItem(hWnd, IDC_MENU_TEXT);
787     list = GetDlgItem(hWnd, IDC_MENU_LIST);
788     if (data->type == MENU_TYPE_TEXT) {
789         ShowWindow(list, SW_HIDE);
790         EnableWindow(list, FALSE);
791         EnableWindow(text, TRUE);
792         ShowWindow(text, SW_SHOW);
793         if (data->is_active)
794             SetFocus(text);
795     } else {
796         ShowWindow(text, SW_HIDE);
797         EnableWindow(text, FALSE);
798         EnableWindow(list, TRUE);
799         ShowWindow(list, SW_SHOW);
800         if (data->is_active)
801             SetFocus(list);
802     }
803     LayoutMenu(hWnd);
804 }
805 /*-----------------------------------------------------------------------------*/
806 void
807 SetMenuListType(HWND hWnd, int how)
808 {
809     PNHMenuWindow data;
810     RECT rt;
811     DWORD dwStyles;
812     char buf[BUFSZ];
813     TCHAR wbuf[BUFSZ];
814     int nItem;
815     int i;
816     HWND control;
817     LVCOLUMN lvcol;
818     LRESULT fnt;
819
820     data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
821     if (data->type != MENU_TYPE_MENU)
822         return;
823
824     data->how = how;
825
826     switch (how) {
827     case PICK_NONE:
828         dwStyles = WS_VISIBLE | WS_TABSTOP | WS_CHILD | WS_VSCROLL
829                    | WS_HSCROLL | LVS_REPORT | LVS_OWNERDRAWFIXED
830                    | LVS_SINGLESEL;
831         break;
832     case PICK_ONE:
833         dwStyles = WS_VISIBLE | WS_TABSTOP | WS_CHILD | WS_VSCROLL
834                    | WS_HSCROLL | LVS_REPORT | LVS_OWNERDRAWFIXED
835                    | LVS_SINGLESEL;
836         break;
837     case PICK_ANY:
838         dwStyles = WS_VISIBLE | WS_TABSTOP | WS_CHILD | WS_VSCROLL
839                    | WS_HSCROLL | LVS_REPORT | LVS_OWNERDRAWFIXED
840                    | LVS_SINGLESEL;
841         break;
842     default:
843         panic("how should be one of PICK_NONE, PICK_ONE or PICK_ANY");
844     };
845
846     if (strlen(data->menu.prompt) == 0) {
847         dwStyles |= LVS_NOCOLUMNHEADER;
848     }
849
850     GetWindowRect(GetDlgItem(hWnd, IDC_MENU_LIST), &rt);
851     DestroyWindow(GetDlgItem(hWnd, IDC_MENU_LIST));
852     control = CreateWindow(WC_LISTVIEW, NULL, dwStyles, rt.left, rt.top,
853                            rt.right - rt.left, rt.bottom - rt.top, hWnd,
854                            (HMENU) IDC_MENU_LIST, GetNHApp()->hApp, NULL);
855     if (!control)
856         panic("cannot create menu control");
857
858     /* install the hook for the control window procedure */
859     wndProcListViewOrig = (WNDPROC) GetWindowLongPtr(control, GWLP_WNDPROC);
860     SetWindowLongPtr(control, GWLP_WNDPROC, (LONG_PTR) NHMenuListWndProc);
861
862     /* set control colors */
863     ListView_SetBkColor(control, menu_bg_brush ? menu_bg_color
864                                                : (COLORREF) GetSysColor(
865                                                      DEFAULT_COLOR_BG_MENU));
866     ListView_SetTextBkColor(
867         control, menu_bg_brush ? menu_bg_color : (COLORREF) GetSysColor(
868                                                      DEFAULT_COLOR_BG_MENU));
869     ListView_SetTextColor(
870         control, menu_fg_brush ? menu_fg_color : (COLORREF) GetSysColor(
871                                                      DEFAULT_COLOR_FG_MENU));
872
873     /* set control font */
874     fnt = SendMessage(hWnd, WM_GETFONT, (WPARAM) 0, (LPARAM) 0);
875     SendMessage(control, WM_SETFONT, (WPARAM) fnt, (LPARAM) 0);
876
877     /* add column to the list view */
878     MonitorInfo monitorInfo;
879     win10_monitor_info(hWnd, &monitorInfo);
880     ZeroMemory(&lvcol, sizeof(lvcol));
881     lvcol.mask = LVCF_WIDTH | LVCF_TEXT;
882     lvcol.cx = monitorInfo.width;
883     lvcol.pszText = NH_A2W(data->menu.prompt, wbuf, BUFSZ);
884     ListView_InsertColumn(control, 0, &lvcol);
885
886     /* add items to the list view */
887     for (i = 0; i < data->menu.size; i++) {
888         LVITEM lvitem;
889         ZeroMemory(&lvitem, sizeof(lvitem));
890         sprintf(buf, "%c - %s", max(data->menu.items[i].accelerator, ' '),
891                 data->menu.items[i].str);
892
893         lvitem.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
894         lvitem.iItem = i;
895         lvitem.iSubItem = 0;
896         lvitem.state = data->menu.items[i].presel ? LVIS_SELECTED : 0;
897         lvitem.pszText = NH_A2W(buf, wbuf, BUFSZ);
898         lvitem.lParam = (LPARAM) &data->menu.items[i];
899         nItem = (int) SendMessage(control, LB_ADDSTRING, (WPARAM) 0,
900                                   (LPARAM) buf);
901         if (ListView_InsertItem(control, &lvitem) == -1) {
902             panic("cannot insert menu item");
903         }
904     }
905     if (data->is_active)
906         SetFocus(control);
907 }
908 /*-----------------------------------------------------------------------------*/
909 HWND
910 GetMenuControl(HWND hWnd)
911 {
912     PNHMenuWindow data;
913
914     data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
915
916         /* We may continue getting window messages after a window's WM_DESTROY is
917            called.  We need to handle the case that USERDATA has been freed. */
918         if (data == NULL)
919                 return NULL;
920
921     if (data->type == MENU_TYPE_TEXT) {
922         return GetDlgItem(hWnd, IDC_MENU_TEXT);
923     } else {
924         return GetDlgItem(hWnd, IDC_MENU_LIST);
925     }
926 }
927 /*-----------------------------------------------------------------------------*/
928 BOOL
929 onMeasureItem(HWND hWnd, WPARAM wParam, LPARAM lParam)
930 {
931     LPMEASUREITEMSTRUCT lpmis;
932     TEXTMETRIC tm;
933     HGDIOBJ saveFont;
934     HDC hdc;
935     PNHMenuWindow data;
936     RECT list_rect;
937     int i;
938
939     UNREFERENCED_PARAMETER(wParam);
940
941     lpmis = (LPMEASUREITEMSTRUCT) lParam;
942     data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
943     GetClientRect(GetMenuControl(hWnd), &list_rect);
944
945     hdc = GetDC(GetMenuControl(hWnd));
946     cached_font * font = mswin_get_font(NHW_MENU, ATR_INVERSE, hdc, FALSE);
947     saveFont = SelectObject(hdc, font->hFont);
948     GetTextMetrics(hdc, &tm);
949
950     /* Set the height of the list box items to max height of the individual
951      * items */
952     for (i = 0; i < data->menu.size; i++) {
953         if (NHMENU_HAS_GLYPH(data->menu.items[i])
954             && !IS_MAP_ASCII(iflags.wc_map_mode)) {
955             lpmis->itemHeight =
956                 max(lpmis->itemHeight,
957                     (UINT) max(tm.tmHeight, GetNHApp()->mapTile_Y) + 2);
958         } else {
959             lpmis->itemHeight =
960                 max(lpmis->itemHeight, (UINT) max(tm.tmHeight, TILE_Y) + 2);
961         }
962     }
963
964     /* set width to the window width */
965     lpmis->itemWidth = list_rect.right - list_rect.left;
966
967     SelectObject(hdc, saveFont);
968     ReleaseDC(GetMenuControl(hWnd), hdc);
969     return TRUE;
970 }
971 /*-----------------------------------------------------------------------------*/
972 BOOL
973 onDrawItem(HWND hWnd, WPARAM wParam, LPARAM lParam)
974 {
975     LPDRAWITEMSTRUCT lpdis;
976     PNHMenuItem item;
977     PNHMenuWindow data;
978     TEXTMETRIC tm;
979     HGDIOBJ saveFont;
980     HDC tileDC;
981     short ntile;
982     int t_x, t_y;
983     int x, y;
984     TCHAR wbuf[BUFSZ];
985     RECT drawRect;
986     COLORREF OldBg, OldFg, NewBg;
987     char *p, *p1;
988     int column;
989     int spacing = 0;
990
991     int color = NO_COLOR, attr;
992     boolean menucolr = FALSE;
993     double monitorScale = win10_monitor_scale(hWnd);
994     int tileXScaled = (int) (TILE_X * monitorScale);
995     int tileYScaled = (int) (TILE_Y * monitorScale);
996
997     UNREFERENCED_PARAMETER(wParam);
998
999     lpdis = (LPDRAWITEMSTRUCT) lParam;
1000
1001     /* If there are no list box items, skip this message. */
1002     if (lpdis->itemID == -1)
1003         return FALSE;
1004
1005     data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
1006
1007     item = &data->menu.items[lpdis->itemID];
1008
1009     tileDC = CreateCompatibleDC(lpdis->hDC);
1010     cached_font * font = mswin_get_font(NHW_MENU, item->attr, lpdis->hDC, FALSE);
1011     saveFont = SelectObject(lpdis->hDC, font->hFont);
1012     NewBg = menu_bg_brush ? menu_bg_color
1013                           : (COLORREF) GetSysColor(DEFAULT_COLOR_BG_MENU);
1014     OldBg = SetBkColor(lpdis->hDC, NewBg);
1015     OldFg = SetTextColor(lpdis->hDC,
1016                          menu_fg_brush
1017                              ? menu_fg_color
1018                              : (COLORREF) GetSysColor(DEFAULT_COLOR_FG_MENU));
1019
1020     GetTextMetrics(lpdis->hDC, &tm);
1021     spacing = tm.tmAveCharWidth;
1022
1023     /* set initial offset */
1024     x = lpdis->rcItem.left + 1;
1025
1026     /* print check mark and letter */
1027     if (NHMENU_IS_SELECTABLE(*item)) {
1028         char buf[2];
1029         if (data->how != PICK_NONE) {
1030             HBITMAP bmpCheck;
1031             HBITMAP bmpSaved;
1032
1033             switch (item->count) {
1034             case -1:
1035                 bmpCheck = data->bmpChecked;
1036                 break;
1037             case 0:
1038                 bmpCheck = data->bmpNotChecked;
1039                 break;
1040             default:
1041                 bmpCheck = data->bmpCheckedCount;
1042                 break;
1043             }
1044
1045             y = (lpdis->rcItem.bottom + lpdis->rcItem.top - tileYScaled) / 2;
1046             bmpSaved = SelectBitmap(data->bmpDC, bmpCheck);
1047             StretchBlt(lpdis->hDC, x, y, tileXScaled, tileYScaled, 
1048                 data->bmpDC, 0, 0,  CHECK_WIDTH, CHECK_HEIGHT, SRCCOPY);
1049             SelectObject(data->bmpDC, bmpSaved);
1050         }
1051
1052         x += tileXScaled + spacing;
1053
1054         if (item->accelerator != 0) {
1055             buf[0] = item->accelerator;
1056             buf[1] = '\x0';
1057
1058             if (iflags.use_menu_color
1059                 && (menucolr = get_menu_coloring(item->str, &color, &attr))) {
1060                 cached_font * menu_font = mswin_get_font(NHW_MENU, attr, lpdis->hDC, FALSE);
1061                 SelectObject(lpdis->hDC, menu_font->hFont);
1062                 if (color != NO_COLOR)
1063                     SetTextColor(lpdis->hDC, nhcolor_to_RGB(color));
1064             }
1065
1066             SetRect(&drawRect, x, lpdis->rcItem.top, lpdis->rcItem.right,
1067                     lpdis->rcItem.bottom);
1068             DrawText(lpdis->hDC, NH_A2W(buf, wbuf, 2), 1, &drawRect,
1069                      DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
1070         }
1071         x += tm.tmAveCharWidth + tm.tmOverhang + spacing;
1072     } else {
1073         x += tileXScaled + tm.tmAveCharWidth + tm.tmOverhang + 2 * spacing;
1074     }
1075
1076     /* print glyph if present */
1077     if (NHMENU_HAS_GLYPH(*item)) {
1078         if (!IS_MAP_ASCII(iflags.wc_map_mode)) {
1079             HGDIOBJ saveBmp;
1080             double monitorScale = win10_monitor_scale(hWnd);
1081
1082             saveBmp = SelectObject(tileDC, GetNHApp()->bmpMapTiles);
1083             ntile = glyph2tile[item->glyph];
1084             t_x =
1085                 (ntile % GetNHApp()->mapTilesPerLine) * GetNHApp()->mapTile_X;
1086             t_y =
1087                 (ntile / GetNHApp()->mapTilesPerLine) * GetNHApp()->mapTile_Y;
1088
1089             y = (lpdis->rcItem.bottom + lpdis->rcItem.top - tileYScaled) / 2;
1090
1091             if (GetNHApp()->bmpMapTiles == GetNHApp()->bmpTiles) {
1092                 /* using original nethack tiles - apply image transparently */
1093                 (*GetNHApp()->lpfnTransparentBlt)(lpdis->hDC, x, y, 
1094                                           tileXScaled, tileYScaled,
1095                                           tileDC, t_x, t_y, TILE_X, TILE_Y,
1096                                           TILE_BK_COLOR);
1097             } else {
1098                 /* using custom tiles - simple blt */
1099                 StretchBlt(lpdis->hDC, x, y, tileXScaled, tileYScaled, 
1100                     tileDC, t_x, t_y, GetNHApp()->mapTile_X, GetNHApp()->mapTile_Y, SRCCOPY);
1101             }
1102             SelectObject(tileDC, saveBmp);
1103             x += tileXScaled;
1104         } else {
1105             const char *sel_ind;
1106             switch (item->count) {
1107             case -1:
1108                 sel_ind = "+";
1109                 break;
1110             case 0:
1111                 sel_ind = "-";
1112                 break;
1113             default:
1114                 sel_ind = "#";
1115                 break;
1116             }
1117
1118             SetRect(&drawRect, x, lpdis->rcItem.top,
1119                     min(x + tm.tmAveCharWidth, lpdis->rcItem.right),
1120                     lpdis->rcItem.bottom);
1121             DrawText(lpdis->hDC, NH_A2W(sel_ind, wbuf, BUFSZ), 1, &drawRect,
1122                      DT_CENTER | DT_VCENTER | DT_SINGLELINE);
1123             x += tm.tmAveCharWidth;
1124         }
1125     } else {
1126         /* no glyph - need to adjust so help window won't look to cramped */
1127         x += tileXScaled;
1128     }
1129
1130     x += spacing;
1131
1132     /* draw item text */
1133     p1 = item->str;
1134     p = strchr(item->str, '\t');
1135     column = 0;
1136     SetRect(&drawRect, x, lpdis->rcItem.top,
1137             min(x + data->menu.tab_stop_size[0], lpdis->rcItem.right),
1138             lpdis->rcItem.bottom);
1139     for (;;) {
1140         TCHAR wbuf[BUFSZ];
1141         if (p != NULL)
1142             *p = '\0'; /* for time being, view tab field as zstring */
1143         DrawText(lpdis->hDC, NH_A2W(p1, wbuf, BUFSZ), strlen(p1), &drawRect,
1144                  DT_LEFT | DT_VCENTER | DT_SINGLELINE);
1145         if (p != NULL)
1146             *p = '\t';
1147         else /* last string so, */
1148             break;
1149
1150         p1 = p + 1;
1151         p = strchr(p1, '\t');
1152         drawRect.left = drawRect.right + TAB_SEPARATION;
1153         ++column;
1154         drawRect.right = min(drawRect.left + data->menu.tab_stop_size[column],
1155                              lpdis->rcItem.right);
1156     }
1157
1158     /* draw focused item */
1159     if (item->has_focus || (NHMENU_IS_SELECTABLE(*item)
1160                             && data->menu.items[lpdis->itemID].count != -1)) {
1161         RECT client_rt;
1162
1163         GetClientRect(lpdis->hwndItem, &client_rt);
1164         client_rt.right = min(client_rt.right, lpdis->rcItem.right);
1165         if (NHMENU_IS_SELECTABLE(*item)
1166             && data->menu.items[lpdis->itemID].count != 0
1167             && item->glyph != NO_GLYPH) {
1168             if (data->menu.items[lpdis->itemID].count == -1) {
1169                 _stprintf(wbuf, TEXT("Count: All"));
1170             } else {
1171                 _stprintf(wbuf, TEXT("Count: %d"),
1172                           data->menu.items[lpdis->itemID].count);
1173             }
1174
1175             /* TOOD: add blinking for blink text */
1176
1177             cached_font * blink_font = mswin_get_font(NHW_MENU, ATR_BLINK, lpdis->hDC, FALSE);
1178             SelectObject(lpdis->hDC, blink_font->hFont);
1179
1180             /* calculate text rectangle */
1181             SetRect(&drawRect, client_rt.left, lpdis->rcItem.top,
1182                     client_rt.right, lpdis->rcItem.bottom);
1183             DrawText(lpdis->hDC, wbuf, _tcslen(wbuf), &drawRect,
1184                      DT_CALCRECT | DT_RIGHT | DT_VCENTER | DT_SINGLELINE
1185                          | DT_NOPREFIX);
1186
1187             /* erase text rectangle */
1188             drawRect.left =
1189                 max(client_rt.left + 1,
1190                     client_rt.right - (drawRect.right - drawRect.left) - 10);
1191             drawRect.right = client_rt.right - 1;
1192             drawRect.top = lpdis->rcItem.top;
1193             drawRect.bottom = lpdis->rcItem.bottom;
1194             FillRect(lpdis->hDC, &drawRect,
1195                      menu_bg_brush ? menu_bg_brush
1196                                    : SYSCLR_TO_BRUSH(DEFAULT_COLOR_BG_MENU));
1197
1198             /* draw text */
1199             DrawText(lpdis->hDC, wbuf, _tcslen(wbuf), &drawRect,
1200                      DT_RIGHT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
1201         }
1202     }
1203     if (item->has_focus) {
1204         /* draw focus rect */
1205         RECT client_rt;
1206
1207         GetClientRect(lpdis->hwndItem, &client_rt);
1208         SetRect(&drawRect, client_rt.left, lpdis->rcItem.top,
1209                 client_rt.left + ListView_GetColumnWidth(lpdis->hwndItem, 0),
1210                 lpdis->rcItem.bottom);
1211         DrawFocusRect(lpdis->hDC, &drawRect);
1212     }
1213
1214     SetTextColor(lpdis->hDC, OldFg);
1215     SetBkColor(lpdis->hDC, OldBg);
1216     SelectObject(lpdis->hDC, saveFont);
1217     DeleteDC(tileDC);
1218     return TRUE;
1219 }
1220 /*-----------------------------------------------------------------------------*/
1221 BOOL
1222 onListChar(HWND hWnd, HWND hwndList, WORD ch)
1223 {
1224     int i = 0;
1225     PNHMenuWindow data;
1226     int curIndex, topIndex, pageSize;
1227     boolean is_accelerator = FALSE;
1228
1229     data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
1230
1231     is_accelerator = FALSE;
1232     for (i = 0; i < data->menu.size; i++) {
1233         if (data->menu.items[i].accelerator == ch) {
1234             is_accelerator = TRUE;
1235             break;
1236         }
1237     }
1238
1239     /* Don't use switch if input matched an accelerator.  Sometimes
1240      * accelerators can conflict with menu actions.  For example, when
1241      * engraving the extra choice of using fingers matches MENU_UNSELECT_ALL.
1242      */
1243     if (is_accelerator)
1244         goto accelerator;
1245
1246     switch (ch) {
1247     case MENU_FIRST_PAGE:
1248         i = 0;
1249         ListView_SetItemState(hwndList, i, LVIS_FOCUSED, LVIS_FOCUSED);
1250         ListView_EnsureVisible(hwndList, i, FALSE);
1251         return -2;
1252
1253     case MENU_LAST_PAGE:
1254         i = max(0, data->menu.size - 1);
1255         ListView_SetItemState(hwndList, i, LVIS_FOCUSED, LVIS_FOCUSED);
1256         ListView_EnsureVisible(hwndList, i, FALSE);
1257         return -2;
1258
1259     case MENU_NEXT_PAGE:
1260         topIndex = ListView_GetTopIndex(hwndList);
1261         pageSize = ListView_GetCountPerPage(hwndList);
1262         curIndex = ListView_GetNextItem(hwndList, -1, LVNI_FOCUSED);
1263         /* Focus down one page */
1264         i = min(curIndex + pageSize, data->menu.size - 1);
1265         ListView_SetItemState(hwndList, i, LVIS_FOCUSED, LVIS_FOCUSED);
1266         /* Scrollpos down one page */
1267         i = min(topIndex + (2 * pageSize - 1), data->menu.size - 1);
1268         ListView_EnsureVisible(hwndList, i, FALSE);
1269         return -2;
1270
1271     case MENU_PREVIOUS_PAGE:
1272         topIndex = ListView_GetTopIndex(hwndList);
1273         pageSize = ListView_GetCountPerPage(hwndList);
1274         curIndex = ListView_GetNextItem(hwndList, -1, LVNI_FOCUSED);
1275         /* Focus up one page */
1276         i = max(curIndex - pageSize, 0);
1277         ListView_SetItemState(hwndList, i, LVIS_FOCUSED, LVIS_FOCUSED);
1278         /* Scrollpos up one page */
1279         i = max(topIndex - pageSize, 0);
1280         ListView_EnsureVisible(hwndList, i, FALSE);
1281         break;
1282
1283     case MENU_SELECT_ALL:
1284         if (data->how == PICK_ANY) {
1285             reset_menu_count(hwndList, data);
1286             for (i = 0; i < data->menu.size; i++) {
1287                 SelectMenuItem(hwndList, data, i, -1);
1288             }
1289             return -2;
1290         }
1291         break;
1292
1293     case MENU_UNSELECT_ALL:
1294         if (data->how == PICK_ANY) {
1295             reset_menu_count(hwndList, data);
1296             for (i = 0; i < data->menu.size; i++) {
1297                 SelectMenuItem(hwndList, data, i, 0);
1298             }
1299             return -2;
1300         }
1301         break;
1302
1303     case MENU_INVERT_ALL:
1304         if (data->how == PICK_ANY) {
1305             reset_menu_count(hwndList, data);
1306             for (i = 0; i < data->menu.size; i++) {
1307                 SelectMenuItem(hwndList, data, i,
1308                                NHMENU_IS_SELECTED(data->menu.items[i]) ? 0
1309                                                                        : -1);
1310             }
1311             return -2;
1312         }
1313         break;
1314
1315     case MENU_SELECT_PAGE:
1316         if (data->how == PICK_ANY) {
1317             int from, to;
1318             reset_menu_count(hwndList, data);
1319             topIndex = ListView_GetTopIndex(hwndList);
1320             pageSize = ListView_GetCountPerPage(hwndList);
1321             from = max(0, topIndex);
1322             to = min(data->menu.size, from + pageSize);
1323             for (i = from; i < to; i++) {
1324                 SelectMenuItem(hwndList, data, i, -1);
1325             }
1326             return -2;
1327         }
1328         break;
1329
1330     case MENU_UNSELECT_PAGE:
1331         if (data->how == PICK_ANY) {
1332             int from, to;
1333             reset_menu_count(hwndList, data);
1334             topIndex = ListView_GetTopIndex(hwndList);
1335             pageSize = ListView_GetCountPerPage(hwndList);
1336             from = max(0, topIndex);
1337             to = min(data->menu.size, from + pageSize);
1338             for (i = from; i < to; i++) {
1339                 SelectMenuItem(hwndList, data, i, 0);
1340             }
1341             return -2;
1342         }
1343         break;
1344
1345     case MENU_INVERT_PAGE:
1346         if (data->how == PICK_ANY) {
1347             int from, to;
1348             reset_menu_count(hwndList, data);
1349             topIndex = ListView_GetTopIndex(hwndList);
1350             pageSize = ListView_GetCountPerPage(hwndList);
1351             from = max(0, topIndex);
1352             to = min(data->menu.size, from + pageSize);
1353             for (i = from; i < to; i++) {
1354                 SelectMenuItem(hwndList, data, i,
1355                                NHMENU_IS_SELECTED(data->menu.items[i]) ? 0
1356                                                                        : -1);
1357             }
1358             return -2;
1359         }
1360         break;
1361
1362     case MENU_SEARCH:
1363         if (data->how == PICK_ANY || data->how == PICK_ONE) {
1364             char buf[BUFSZ];
1365
1366             reset_menu_count(hwndList, data);
1367             if (mswin_getlin_window("Search for:", buf, BUFSZ) == IDCANCEL) {
1368                 strcpy(buf, "\033");
1369             }
1370             if (data->is_active)
1371                 SetFocus(hwndList); // set focus back to the list control
1372             if (!*buf || *buf == '\033')
1373                 return -2;
1374             for (i = 0; i < data->menu.size; i++) {
1375                 if (NHMENU_IS_SELECTABLE(data->menu.items[i])
1376                     && strstr(data->menu.items[i].str, buf)) {
1377                     if (data->how == PICK_ANY) {
1378                         SelectMenuItem(
1379                             hwndList, data, i,
1380                             NHMENU_IS_SELECTED(data->menu.items[i]) ? 0 : -1);
1381                     } else if (data->how == PICK_ONE) {
1382                         SelectMenuItem(hwndList, data, i, -1);
1383                         ListView_SetItemState(hwndList, i, LVIS_FOCUSED,
1384                                               LVIS_FOCUSED);
1385                         ListView_EnsureVisible(hwndList, i, FALSE);
1386                         break;
1387                     }
1388                 }
1389             }
1390         } else {
1391             mswin_nhbell();
1392         }
1393         return -2;
1394
1395     case ' ': {
1396         if (GetNHApp()->regNetHackMode) {
1397             /* NetHack mode: Scroll down one page,
1398                ends menu when on last page. */
1399             SCROLLINFO si;
1400
1401             si.cbSize = sizeof(SCROLLINFO);
1402             si.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
1403             GetScrollInfo(hwndList, SB_VERT, &si);
1404             if ((si.nPos + (int) si.nPage) > (si.nMax - si.nMin)) {
1405                 /* We're at the bottom: dismiss. */
1406                 data->done = 1;
1407                 data->result = 0;
1408                 return -2;
1409             }
1410             /* We're not at the bottom: page down. */
1411             topIndex = ListView_GetTopIndex(hwndList);
1412             pageSize = ListView_GetCountPerPage(hwndList);
1413             curIndex = ListView_GetNextItem(hwndList, -1, LVNI_FOCUSED);
1414             /* Focus down one page */
1415             i = min(curIndex + pageSize, data->menu.size - 1);
1416             ListView_SetItemState(hwndList, i, LVIS_FOCUSED, LVIS_FOCUSED);
1417             /* Scrollpos down one page */
1418             i = min(topIndex + (2 * pageSize - 1), data->menu.size - 1);
1419             ListView_EnsureVisible(hwndList, i, FALSE);
1420
1421             return -2;
1422         } else {
1423             /* Windows mode: ends menu for PICK_ONE/PICK_NONE
1424                select item for PICK_ANY */
1425             if (data->how == PICK_ONE || data->how == PICK_NONE) {
1426                 data->done = 1;
1427                 data->result = 0;
1428                 return -2;
1429             } else if (data->how == PICK_ANY) {
1430                 i = ListView_GetNextItem(hwndList, -1, LVNI_FOCUSED);
1431                 if (i >= 0) {
1432                     SelectMenuItem(
1433                         hwndList, data, i,
1434                         NHMENU_IS_SELECTED(data->menu.items[i]) ? 0 : -1);
1435                 }
1436             }
1437         }
1438     } break;
1439
1440     accelerator:
1441     default:
1442         if (strchr(data->menu.gacc, ch)
1443             && !(ch == '0' && data->menu.counting)) {
1444             /* matched a group accelerator */
1445             if (data->how == PICK_ANY || data->how == PICK_ONE) {
1446                 reset_menu_count(hwndList, data);
1447                 for (i = 0; i < data->menu.size; i++) {
1448                     if (NHMENU_IS_SELECTABLE(data->menu.items[i])
1449                         && data->menu.items[i].group_accel == ch) {
1450                         if (data->how == PICK_ANY) {
1451                             SelectMenuItem(
1452                                 hwndList, data, i,
1453                                 NHMENU_IS_SELECTED(data->menu.items[i]) ? 0
1454                                                                         : -1);
1455                         } else if (data->how == PICK_ONE) {
1456                             SelectMenuItem(hwndList, data, i, -1);
1457                             data->result = 0;
1458                             data->done = 1;
1459                             return -2;
1460                         }
1461                     }
1462                 }
1463                 return -2;
1464             } else {
1465                 mswin_nhbell();
1466                 return -2;
1467             }
1468         }
1469
1470         if (isdigit((uchar) ch)) {
1471             int count;
1472             i = ListView_GetNextItem(hwndList, -1, LVNI_FOCUSED);
1473             if (i >= 0) {
1474                 count = data->menu.items[i].count;
1475                 if (count == -1)
1476                     count = 0;
1477                 count *= 10L;
1478                 count += (int) (ch - '0');
1479                 if (count != 0) /* ignore leading zeros */ {
1480                     data->menu.counting = TRUE;
1481                     data->menu.items[i].count = min(100000, count);
1482                     ListView_RedrawItems(hwndList, i,
1483                                          i); /* update count mark */
1484                 }
1485             }
1486             return -2;
1487         }
1488
1489         if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
1490             || is_accelerator) {
1491             if (data->how == PICK_ANY || data->how == PICK_ONE) {
1492                 topIndex = ListView_GetTopIndex(hwndList);
1493                 if( topIndex < 0 || topIndex > data->menu.size ) break; // impossible?
1494                 int iter = topIndex;
1495                 do {
1496                     i = iter % data->menu.size;
1497                     if (data->menu.items[i].accelerator == ch) {
1498                         if (data->how == PICK_ANY) {
1499                             SelectMenuItem(
1500                                 hwndList, data, i,
1501                                 NHMENU_IS_SELECTED(data->menu.items[i]) ? 0
1502                                                                         : -1);
1503                             ListView_SetItemState(hwndList, i, LVIS_FOCUSED,
1504                                                   LVIS_FOCUSED);
1505                             ListView_EnsureVisible(hwndList, i, FALSE);
1506                             return -2;
1507                         } else if (data->how == PICK_ONE) {
1508                             SelectMenuItem(hwndList, data, i, -1);
1509                             data->result = 0;
1510                             data->done = 1;
1511                             return -2;
1512                         }
1513                     }
1514                 } while( (++iter % data->menu.size) != topIndex ); 
1515             }
1516         }
1517         break;
1518     }
1519
1520     reset_menu_count(hwndList, data);
1521     return -1;
1522 }
1523 /*-----------------------------------------------------------------------------*/
1524 void
1525 mswin_menu_window_size(HWND hWnd, LPSIZE sz)
1526 {
1527     HWND control;
1528     PNHMenuWindow data;
1529     RECT rt, wrt;
1530     int extra_cx;
1531
1532     data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
1533     if (data) {
1534         control = GetMenuControl(hWnd);
1535
1536         /* get the control size */
1537         GetClientRect(control, &rt);
1538         sz->cx = rt.right - rt.left;
1539         sz->cy = rt.bottom - rt.top;
1540
1541         /* calculate "extra" space around the control */
1542         GetWindowRect(hWnd, &wrt);
1543         extra_cx = (wrt.right - wrt.left) - sz->cx;
1544
1545         if (data->type == MENU_TYPE_MENU) {
1546             sz->cx = data->menu.menu_cx + GetSystemMetrics(SM_CXVSCROLL);
1547         } else {
1548             /* Use the width of the text box */
1549             sz->cx = data->text.text_box_size.cx
1550                      + 2 * GetSystemMetrics(SM_CXVSCROLL);
1551         }
1552         sz->cx += extra_cx;
1553     } else {
1554         /* uninitilized window */
1555         GetClientRect(hWnd, &rt);
1556         sz->cx = rt.right - rt.left;
1557         sz->cy = rt.bottom - rt.top;
1558     }
1559 }
1560 /*-----------------------------------------------------------------------------*/
1561 void
1562 SelectMenuItem(HWND hwndList, PNHMenuWindow data, int item, int count)
1563 {
1564     int i;
1565
1566     if (item < 0 || item >= data->menu.size)
1567         return;
1568
1569     if (data->how == PICK_ONE && count != 0) {
1570         for (i = 0; i < data->menu.size; i++)
1571             if (item != i && data->menu.items[i].count != 0) {
1572                 data->menu.items[i].count = 0;
1573                 ListView_RedrawItems(hwndList, i, i);
1574             };
1575     }
1576
1577     data->menu.items[item].count = count;
1578     ListView_RedrawItems(hwndList, item, item);
1579     reset_menu_count(hwndList, data);
1580 }
1581 /*-----------------------------------------------------------------------------*/
1582 void
1583 reset_menu_count(HWND hwndList, PNHMenuWindow data)
1584 {
1585     int i;
1586     data->menu.counting = FALSE;
1587     if (IsWindow(hwndList)) {
1588         i = ListView_GetNextItem((hwndList), -1, LVNI_FOCUSED);
1589         if (i >= 0)
1590             ListView_RedrawItems(hwndList, i, i);
1591     }
1592 }
1593 /*-----------------------------------------------------------------------------*/
1594 /* List window Proc */
1595 LRESULT CALLBACK
1596 NHMenuListWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
1597 {
1598     HWND hWndParent = GetParent(hWnd);
1599     BOOL bUpdateFocusItem;
1600
1601     /* we will redraw focused item whenever horizontal scrolling occurs
1602        since "Count: XXX" indicator is garbled by scrolling */
1603     bUpdateFocusItem = FALSE;
1604
1605     switch (message) {
1606     case WM_KEYDOWN:
1607         if (wParam == VK_LEFT || wParam == VK_RIGHT)
1608             bUpdateFocusItem = TRUE;
1609         break;
1610
1611     case WM_CHAR: /* filter keyboard input for the control */
1612         if (wParam > 0 && wParam < 256
1613             && onListChar(GetParent(hWnd), hWnd, (char) wParam) == -2) {
1614             return 0;
1615         } else {
1616             return 1;
1617         }
1618         break;
1619
1620     case WM_SIZE:
1621     case WM_HSCROLL:
1622         bUpdateFocusItem = TRUE;
1623         break;
1624
1625     case WM_SETFOCUS:
1626         if (GetParent(hWnd) != GetNHApp()->hPopupWnd) {
1627             SetFocus(GetNHApp()->hMainWnd);
1628         }
1629         return FALSE;
1630
1631     case WM_MSNH_COMMAND:
1632         if (wParam == MSNH_MSG_RANDOM_INPUT) {
1633             char c = randomkey();
1634             if (c == '\n')
1635                 PostMessage(hWndParent, WM_COMMAND, MAKELONG(IDOK, 0), 0);
1636             else if (c == '\033')
1637                 PostMessage(hWndParent, WM_COMMAND, MAKELONG(IDCANCEL, 0), 0);
1638             else
1639                 PostMessage(hWnd, WM_CHAR, c, 0);
1640             return 0;
1641         }
1642         break;
1643
1644     }
1645
1646     /* update focused item */
1647     if (bUpdateFocusItem) {
1648         int i;
1649         RECT rt;
1650
1651         /* invalidate the focus rectangle */
1652         i = ListView_GetNextItem(hWnd, -1, LVNI_FOCUSED);
1653         if (i != -1) {
1654             ListView_GetItemRect(hWnd, i, &rt, LVIR_BOUNDS);
1655             InvalidateRect(hWnd, &rt, TRUE);
1656         }
1657     }
1658
1659     /* call ListView control window proc */
1660     if (wndProcListViewOrig)
1661         return CallWindowProc(wndProcListViewOrig, hWnd, message, wParam,
1662                               lParam);
1663     else
1664         return 0;
1665 }
1666 /*-----------------------------------------------------------------------------*/
1667 /* Text control window proc - implements scrolling without a cursor */
1668 LRESULT CALLBACK
1669 NHMenuTextWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
1670 {
1671     HWND hWndParent = GetParent(hWnd);
1672     HDC hDC;
1673     RECT rc;
1674
1675     switch (message) {
1676     case WM_ERASEBKGND:
1677         hDC = (HDC) wParam;
1678         GetClientRect(hWnd, &rc);
1679         FillRect(hDC, &rc, text_bg_brush
1680                                ? text_bg_brush
1681                                : SYSCLR_TO_BRUSH(DEFAULT_COLOR_BG_TEXT));
1682         return 0;
1683
1684     case WM_KEYDOWN:
1685         switch (wParam) {
1686         /* close on space in Windows mode
1687            page down on space in NetHack mode */
1688         case VK_SPACE: {
1689             SCROLLINFO si;
1690
1691             si.cbSize = sizeof(SCROLLINFO);
1692             si.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
1693             GetScrollInfo(hWnd, SB_VERT, &si);
1694             /* If nethackmode and not at the end of the list */
1695             if (GetNHApp()->regNetHackMode
1696                 && (si.nPos + (int) si.nPage) <= (si.nMax - si.nMin))
1697                 SendMessage(hWnd, EM_SCROLL, SB_PAGEDOWN, 0);
1698             else
1699                 PostMessage(hWndParent, WM_COMMAND, MAKELONG(IDOK, 0), 0);
1700             return 0;
1701         }
1702         case VK_NEXT:
1703             SendMessage(hWnd, EM_SCROLL, SB_PAGEDOWN, 0);
1704             return 0;
1705         case VK_PRIOR:
1706             SendMessage(hWnd, EM_SCROLL, SB_PAGEUP, 0);
1707             return 0;
1708         case VK_UP:
1709             SendMessage(hWnd, EM_SCROLL, SB_LINEUP, 0);
1710             return 0;
1711         case VK_DOWN:
1712             SendMessage(hWnd, EM_SCROLL, SB_LINEDOWN, 0);
1713             return 0;
1714         }
1715         break;
1716
1717     case WM_CHAR:
1718         switch(wParam) {
1719         case MENU_FIRST_PAGE:
1720             SendMessage(hWnd, EM_SCROLL, SB_TOP, 0);
1721             return 0;
1722         case MENU_LAST_PAGE:
1723             SendMessage(hWnd, EM_SCROLL, SB_BOTTOM, 0);
1724             return 0;
1725         case MENU_NEXT_PAGE:
1726             SendMessage(hWnd, EM_SCROLL, SB_PAGEDOWN, 0);
1727             return 0;
1728         case MENU_PREVIOUS_PAGE:
1729             SendMessage(hWnd, EM_SCROLL, SB_PAGEUP, 0);
1730             return 0;
1731         }
1732         break;
1733
1734     /* edit control needs to know nothing of its focus */
1735     case WM_SETFOCUS:
1736         HideCaret(hWnd);
1737         return 0;
1738
1739     case WM_MSNH_COMMAND:
1740         if (wParam == MSNH_MSG_RANDOM_INPUT) {
1741             char c = randomkey();
1742             if (c == '\n')
1743                 PostMessage(hWndParent, WM_COMMAND, MAKELONG(IDOK, 0), 0);
1744             else if (c == '\033')
1745                 PostMessage(hWndParent, WM_COMMAND, MAKELONG(IDCANCEL, 0), 0);
1746             else
1747                 PostMessage(hWnd, WM_CHAR, c, 0);
1748             return 0;
1749         }
1750         break;
1751
1752     }
1753
1754     if (editControlWndProc)
1755         return CallWindowProc(editControlWndProc, hWnd, message, wParam,
1756                               lParam);
1757     else
1758         return 0;
1759 }
1760 /*-----------------------------------------------------------------------------*/