OSDN Git Service

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