Files
nethack/win/win32/mhmenu.c
nhmall 1f6c1d0f42 expand the glyphs
The walls for the mines, gehennom, knox, and sokoban had been
changed at the "tile"-level, with no awareness of the core game,
or non-tile interfaces.
- Expand the glyphs to include a set of walls for the main level
as well as each of those mentioned above.

Altars had been adjusted at the map_glyphinfo() level to substitute
some color variations on-the-fly for unaligned, chaotic, neutral,
lawful altars, and shrines. The tile interface had no awareness of
the feature.
- Expand the glyphs to include each of the altar variations that
had been implemented in the display code for tty-only. This required
the addition of four placeholder tiles in other.txt. Someone with
artistic skill will hopefully alter the additional tiles to better
reflect their intended purpose.

Explosions had unique tiles in the tile window port, and the display
code for tty tinkered with the colors, but the game had very little
awareness of the different types of explosions.
- Expand the glyphs to include each of the explosion types: dark,
noxious, muddy, wet, magical, fiery and frosty.

Pile-markers to represent a pile had been introduced at the
display-level, without little to no awareness by the core game.
- Expand the glyphs to include piletops, including objects,
bodys, and statues.

Recently male and female variations of tiles and monsters had been
had been introduced, but the mechanics had been mostly done at the
display-level through a marker flag. The window port interface then
had to increment the tile mapped to the glyph to get the female version
of the tile.
- Expand the glyphs to include the male and female versions of the
monsters, and their corresponding pet versions, ridden, detected
versions and statues of them.

Direct references to GLYPH_BODY_OFF and GLYPH_STATUE_OFF
in object_from_map() in pager.c were getting incomplete results.
- Add macros glyph_to_body_corpsenm(glyph) and
glyph_to_statue_corpsenm(glyph) macros for obtaining the corpsenm
value after passing the glyph_is_body() or glyph_is_statue() test.

Other relevant notes:

- The tile ordering in the win/share/*.txt tile files has been altered,
other.txt in particular.

- tilemap.c has had a lot of alterations to accommodate the expanded
glyphs. Output that is useful for troubleshooting will end up in
tilemappings.lst if OBTAIN_TILEMAP is defined during build.
It lists all of the glyphs and which tile it gets mapped to, and also
lists each tile and some of the references to it by various glyphs.

- An array glyphmap[MAXGLYPH] is now used. It has an entry for each
glyph, ordered by glyph, and once reset_glyphs(glyph) has been run, it
contains the mapped symindex, default color, glyphflags, and tile
index.
If USE_TILES is defined during build, the tile.c produced from the
tilemap utility populates the tileidx field of each array element with
a glyph-to-tile mapping for the glyph. Later on, when reset_glyphmap()
is run, the other fields of each element will get populated.

- The glyph-to-tile mapping is an added field available to a window
port via the glyphinfo struct passed in the documented interface. The
old glyph2tile[] array is gone. The various active window ports that
had been using glyph2tile[] have been updated to use the new interface
mechanism. Disclaimer: There may be some bug fixing or tidying
required in the window port code.

- reset_glyphmap() is called after config file options parsing
has finished, because some config file settings can impact the results
produced by reset_glyphmap().

- Everything that passes the glyph_is_cmap(glyph) test must
return a valid cmap value from glyph_to_cmap(glyph).

- An 'extern glyph_info glyphmap[MAX_GLYPH];' is inserted into the
top of only the files which need awareness of it, not inserted into
display.h. Presently, the only files that actually need to directly
reference the glyphmap[] array are display.c, o_init.c (for shuffling
the tiles), and the generated tile.c (if USE_TILES is defined).

- Added an MG_MALE glyphflag to complement the MG_FEMALE glyphflag.

- Provide an array for wall colorizations. reset_glyphmap() will draw
the colors from this array: int array wallcolors[sokoban_walls + 1];
The indices of the wallcolors array are main_walls (0), mines_walls
(1), gehennom_walls (2), knox_walls (3), and sokoban_walls (4).
In future, a config file option for adjusting the wall colors and/or
an 'O' option menu to do the same could be added. Right now, the
initializaton of the wallcolors[] array entries in display.c leaves the
walls at CLR_GRAY, matching the defsym color.

- Most of the display-level kludges for some of the on-the-fly
interface features have been removed from map_glyphinfo() as they
aren't needed any longer. These glyph expansions adhere more closely to
the original glyph mechanics of the game.

- Because the glyphs are re-ordered and expanded, an update to
editlevel will be required upon merge of these changes.
2021-09-18 19:51:04 -04:00

1770 lines
61 KiB
C

/* NetHack 3.7 mhmenu.c $NHDT-Date: 1596498354 2020/08/03 23:45:54 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.74 $ */
/* Copyright (c) Alex Kompel, 2002 */
/* NetHack may be freely redistributed. See license for details. */
#include "win10.h"
#include "winMS.h"
#include <assert.h>
#include "resource.h"
#include "mhmenu.h"
#include "mhmain.h"
#include "mhmsg.h"
#include "mhfont.h"
#include "mhdlg.h"
#define MENU_MARGIN 0
#define NHMENU_STR_SIZE BUFSZ
#define MIN_TABSTOP_SIZE 0
#define NUMTABS 15
#define TAB_SEPARATION 10 /* pixels between each tab stop */
#define DEFAULT_COLOR_BG_TEXT COLOR_WINDOW
#define DEFAULT_COLOR_FG_TEXT COLOR_WINDOWTEXT
#define DEFAULT_COLOR_BG_MENU COLOR_WINDOW
#define DEFAULT_COLOR_FG_MENU COLOR_WINDOWTEXT
#define CHECK_WIDTH 16
#define CHECK_HEIGHT 16
typedef struct mswin_menu_item {
glyph_info glyphinfo;
ANY_P identifier;
char accelerator;
char group_accel;
int attr;
char str[NHMENU_STR_SIZE];
boolean presel;
unsigned int itemflags;
int count;
BOOL has_focus;
} NHMenuItem, *PNHMenuItem;
union menu_innards {
struct menu_list {
int size; /* number of items in items[] */
int allocated; /* number of allocated slots in items[] */
PNHMenuItem items; /* menu items */
char gacc[QBUFSZ]; /* group accelerators */
BOOL counting; /* counting flag */
char prompt[QBUFSZ]; /* menu prompt */
int tab_stop_size[NUMTABS]; /* tabstops to align option values */
int menu_cx; /* menu width */
} menu;
struct menu_text {
TCHAR *text;
SIZE text_box_size;
} text;
};
typedef struct mswin_nethack_menu_window {
int type; /* MENU_TYPE_TEXT or MENU_TYPE_MENU */
int how; /* for menus: PICK_NONE, PICK_ONE, PICK_ANY */
union menu_innards menui;
int result;
int done;
HBITMAP bmpChecked;
HBITMAP bmpCheckedCount;
HBITMAP bmpNotChecked;
HDC bmpDC;
BOOL is_active;
} NHMenuWindow, *PNHMenuWindow;
static WNDPROC wndProcListViewOrig = NULL;
static WNDPROC editControlWndProc = NULL;
#define NHMENU_IS_SELECTABLE(item) ((item).identifier.a_obj != NULL)
#define NHMENU_IS_SELECTED(item) ((item).count != 0)
#define NHMENU_HAS_GLYPH(item) ((item).glyphinfo.glyph != NO_GLYPH)
INT_PTR CALLBACK MenuWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK NHMenuListWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK NHMenuTextWndProc(HWND, UINT, WPARAM, LPARAM);
static void onMSNHCommand(HWND hWnd, WPARAM wParam, LPARAM lParam);
static BOOL onMeasureItem(HWND hWnd, WPARAM wParam, LPARAM lParam);
static BOOL onDrawItem(HWND hWnd, WPARAM wParam, LPARAM lParam);
static void LayoutMenu(HWND hwnd);
static void SetMenuType(HWND hwnd, int type);
static void SetMenuListType(HWND hwnd, int now);
static HWND GetMenuControl(HWND hwnd);
static void SelectMenuItem(HWND hwndList, PNHMenuWindow data, int item,
int count);
static void reset_menu_count(HWND hwndList, PNHMenuWindow data);
static BOOL onListChar(HWND hWnd, HWND hwndList, WORD ch);
/*-----------------------------------------------------------------------------*/
HWND
mswin_init_menu_window(int type)
{
HWND ret;
RECT rt;
/* get window position */
if (GetNHApp()->bAutoLayout) {
SetRect(&rt, 0, 0, 0, 0);
} else {
mswin_get_window_placement(NHW_MENU, &rt);
}
/* create menu window object */
ret = CreateDialog(GetNHApp()->hApp, MAKEINTRESOURCE(IDD_MENU),
GetNHApp()->hMainWnd, MenuWndProc);
if (!ret) {
panic("Cannot create menu window");
}
/* move it in the predefined position */
if (!GetNHApp()->bAutoLayout) {
MoveWindow(ret, rt.left, rt.top, rt.right - rt.left,
rt.bottom - rt.top, TRUE);
}
/* Set window caption */
SetWindowText(ret, "Menu/Text");
mswin_apply_window_style(ret);
SetMenuType(ret, type);
return ret;
}
/*-----------------------------------------------------------------------------*/
int
mswin_menu_window_select_menu(HWND hWnd, int how, MENU_ITEM_P **_selected,
BOOL activate)
{
PNHMenuWindow data;
int ret_val;
MENU_ITEM_P *selected = NULL;
int i;
char *ap;
assert(_selected != NULL);
*_selected = NULL;
ret_val = -1;
data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
/* force activate for certain menu types */
if (data->type == MENU_TYPE_MENU
&& (how == PICK_ONE || how == PICK_ANY)) {
activate = TRUE;
}
data->is_active = activate && !GetNHApp()->regNetHackMode;
/* set menu type */
SetMenuListType(hWnd, how);
/* Ok, now give items a unique accelerators */
if (data->type == MENU_TYPE_MENU) {
char next_char = 'a';
data->menui.menu.gacc[0] = '\0';
ap = data->menui.menu.gacc;
for (i = 0; i < data->menui.menu.size; i++) {
if (data->menui.menu.items[i].accelerator != 0) {
if (isalpha(data->menui.menu.items[i].accelerator)) {
next_char = (char)(data->menui.menu.items[i].accelerator + 1);
}
} else if (NHMENU_IS_SELECTABLE(data->menui.menu.items[i])) {
if (isalpha(next_char)) {
data->menui.menu.items[i].accelerator = next_char;
} else {
if (next_char > 'z')
next_char = 'A';
else if (next_char > 'Z')
next_char = 'a';
data->menui.menu.items[i].accelerator = next_char;
}
next_char++;
}
}
/* collect group accelerators */
for (i = 0; i < data->menui.menu.size; i++) {
if (data->how != PICK_NONE) {
if (data->menui.menu.items[i].group_accel
&& !strchr(data->menui.menu.gacc,
data->menui.menu.items[i].group_accel)) {
*ap++ = data->menui.menu.items[i].group_accel;
*ap = '\x0';
}
}
}
reset_menu_count(NULL, data);
}
LayoutMenu(hWnd); // show dialog buttons
if (activate) {
mswin_popup_display(hWnd, &data->done);
} else {
SetFocus(GetNHApp()->hMainWnd);
mswin_layout_main_window(hWnd);
}
/* get the result */
if (data->result != -1) {
if (how == PICK_NONE) {
if (data->result >= 0)
ret_val = 0;
else
ret_val = -1;
} else if (how == PICK_ONE || how == PICK_ANY) {
/* count selected items */
ret_val = 0;
for (i = 0; i < data->menui.menu.size; i++) {
if (NHMENU_IS_SELECTABLE(data->menui.menu.items[i])
&& NHMENU_IS_SELECTED(data->menui.menu.items[i])) {
ret_val++;
}
}
if (ret_val > 0) {
int sel_ind;
selected =
(MENU_ITEM_P *) malloc(ret_val * sizeof(MENU_ITEM_P));
if (!selected)
panic("out of memory");
sel_ind = 0;
for (i = 0; i < data->menui.menu.size; i++) {
if (NHMENU_IS_SELECTABLE(data->menui.menu.items[i])
&& NHMENU_IS_SELECTED(data->menui.menu.items[i])) {
selected[sel_ind].item =
data->menui.menu.items[i].identifier;
selected[sel_ind].count = data->menui.menu.items[i].count;
sel_ind++;
}
}
ret_val = sel_ind;
*_selected = selected;
}
}
}
if (activate) {
data->is_active = FALSE;
LayoutMenu(hWnd); // hide dialog buttons
mswin_popup_destroy(hWnd);
/* If we just used the permanent inventory window to pick something,
* set the menu back to its display inventory state.
*/
if (iflags.perm_invent && mswin_winid_from_handle(hWnd) == WIN_INVEN
&& how != PICK_NONE) {
data->menui.menu.prompt[0] = '\0';
SetMenuListType(hWnd, PICK_NONE);
for (i = 0; i < data->menui.menu.size; i++)
data->menui.menu.items[i].count = 0;
LayoutMenu(hWnd);
}
}
return ret_val;
}
/*-----------------------------------------------------------------------------*/
INT_PTR CALLBACK
MenuWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PNHMenuWindow data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
HWND control = GetDlgItem(hWnd, IDC_MENU_TEXT);
TCHAR title[MAX_LOADSTRING];
switch (message) {
case WM_INITDIALOG: {
HDC hdc = GetDC(control);
data = (PNHMenuWindow) malloc(sizeof(NHMenuWindow));
ZeroMemory(data, sizeof(NHMenuWindow));
data->type = MENU_TYPE_TEXT;
data->how = PICK_NONE;
data->result = 0;
data->done = 0;
data->bmpChecked =
LoadBitmap(GetNHApp()->hApp, MAKEINTRESOURCE(IDB_MENU_SEL));
data->bmpCheckedCount =
LoadBitmap(GetNHApp()->hApp, MAKEINTRESOURCE(IDB_MENU_SEL_COUNT));
data->bmpNotChecked =
LoadBitmap(GetNHApp()->hApp, MAKEINTRESOURCE(IDB_MENU_UNSEL));
data->bmpDC = CreateCompatibleDC(hdc);
data->is_active = FALSE;
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) data);
/* set font for the text cotrol */
cached_font * font = mswin_get_font(NHW_MENU, ATR_NONE, hdc, FALSE);
SendMessage(control, WM_SETFONT,
(WPARAM) font->hFont,
(LPARAM) 0);
ReleaseDC(control, hdc);
/* subclass edit control */
editControlWndProc =
(WNDPROC) GetWindowLongPtr(control, GWLP_WNDPROC);
SetWindowLongPtr(control, GWLP_WNDPROC, (LONG_PTR) NHMenuTextWndProc);
/* Even though the dialog has no caption, you can still set the title
which shows on Alt-Tab */
LoadString(GetNHApp()->hApp, IDS_APP_TITLE, title, MAX_LOADSTRING);
SetWindowText(hWnd, title);
/* set focus to text control for now */
SetFocus(control);
}
return FALSE;
case WM_MSNH_COMMAND:
onMSNHCommand(hWnd, wParam, lParam);
break;
case WM_SIZE: {
RECT rt;
LayoutMenu(hWnd);
GetWindowRect(hWnd, &rt);
ScreenToClient(GetNHApp()->hMainWnd, (LPPOINT) &rt);
ScreenToClient(GetNHApp()->hMainWnd, ((LPPOINT) &rt) + 1);
if (iflags.perm_invent && mswin_winid_from_handle(hWnd) == WIN_INVEN)
mswin_update_window_placement(NHW_INVEN, &rt);
else
mswin_update_window_placement(NHW_MENU, &rt);
}
return FALSE;
case WM_MOVE: {
RECT rt;
GetWindowRect(hWnd, &rt);
ScreenToClient(GetNHApp()->hMainWnd, (LPPOINT) &rt);
ScreenToClient(GetNHApp()->hMainWnd, ((LPPOINT) &rt) + 1);
if (iflags.perm_invent && mswin_winid_from_handle(hWnd) == WIN_INVEN)
mswin_update_window_placement(NHW_INVEN, &rt);
else
mswin_update_window_placement(NHW_MENU, &rt);
}
return FALSE;
case WM_CLOSE:
if (g.program_state.gameover) {
data->result = -1;
data->done = 1;
g.program_state.stopprint++;
return TRUE;
} else
return FALSE;
case WM_COMMAND: {
switch (LOWORD(wParam)) {
case IDCANCEL:
if (data->type == MENU_TYPE_MENU
&& (data->how == PICK_ONE || data->how == PICK_ANY)
&& data->menui.menu.counting) {
HWND list;
int i;
/* reset counter if counting is in progress */
list = GetMenuControl(hWnd);
i = ListView_GetNextItem(list, -1, LVNI_FOCUSED);
if (i >= 0) {
SelectMenuItem(list, data, i, 0);
}
return TRUE;
} else {
data->result = -1;
data->done = 1;
}
return TRUE;
case IDOK:
data->done = 1;
data->result = 0;
return TRUE;
}
} break;
case WM_NOTIFY: {
LPNMHDR lpnmhdr = (LPNMHDR) lParam;
switch (LOWORD(wParam)) {
case IDC_MENU_LIST: {
if (!data || data->type != MENU_TYPE_MENU)
break;
switch (lpnmhdr->code) {
case LVN_ITEMACTIVATE: {
LPNMLISTVIEW lpnmlv = (LPNMLISTVIEW) lParam;
if (data->how == PICK_ONE) {
if (lpnmlv->iItem >= 0 && lpnmlv->iItem < data->menui.menu.size
&& NHMENU_IS_SELECTABLE(
data->menui.menu.items[lpnmlv->iItem])) {
SelectMenuItem(lpnmlv->hdr.hwndFrom, data,
lpnmlv->iItem, -1);
data->done = 1;
data->result = 0;
return TRUE;
}
}
} break;
case NM_CLICK: {
LPNMLISTVIEW lpnmitem = (LPNMLISTVIEW) lParam;
if (lpnmitem->iItem == -1)
return 0;
if (data->how == PICK_ANY) {
SelectMenuItem(
lpnmitem->hdr.hwndFrom, data, lpnmitem->iItem,
NHMENU_IS_SELECTED(data->menui.menu.items[lpnmitem->iItem])
? 0
: -1);
}
} break;
case LVN_ITEMCHANGED: {
LPNMLISTVIEW lpnmlv = (LPNMLISTVIEW) lParam;
if (lpnmlv->iItem == -1)
return 0;
if (!(lpnmlv->uChanged & LVIF_STATE))
return 0;
if (data->how == PICK_ONE || data->how == PICK_ANY) {
data->menui.menu.items[lpnmlv->iItem].has_focus =
!!(lpnmlv->uNewState & LVIS_FOCUSED);
ListView_RedrawItems(lpnmlv->hdr.hwndFrom, lpnmlv->iItem,
lpnmlv->iItem);
}
/* update count for single-selection menu (follow the listview
* selection) */
if (data->how == PICK_ONE) {
if (lpnmlv->uNewState & LVIS_SELECTED) {
SelectMenuItem(lpnmlv->hdr.hwndFrom, data,
lpnmlv->iItem, -1);
}
}
/* check item focus */
if (data->how == PICK_ONE || data->how == PICK_ANY) {
data->menui.menu.items[lpnmlv->iItem].has_focus =
!!(lpnmlv->uNewState & LVIS_FOCUSED);
ListView_RedrawItems(lpnmlv->hdr.hwndFrom, lpnmlv->iItem,
lpnmlv->iItem);
}
} break;
case NM_KILLFOCUS:
reset_menu_count(lpnmhdr->hwndFrom, data);
break;
}
} break;
}
} break;
case WM_SETFOCUS:
if (hWnd != GetNHApp()->hPopupWnd) {
SetFocus(GetNHApp()->hMainWnd);
} else {
if (IsWindow(GetMenuControl(hWnd)))
SetFocus(GetMenuControl(hWnd));
}
return FALSE;
case WM_MEASUREITEM:
if (wParam == IDC_MENU_LIST)
return onMeasureItem(hWnd, wParam, lParam);
else
return FALSE;
case WM_DRAWITEM:
if (wParam == IDC_MENU_LIST)
return onDrawItem(hWnd, wParam, lParam);
else
return FALSE;
case WM_CTLCOLORSTATIC: { /* sent by edit control before it is drawn */
HDC hdcEdit = (HDC) wParam;
HWND hwndEdit = (HWND) lParam;
if (hwndEdit == GetDlgItem(hWnd, IDC_MENU_TEXT)) {
SetBkColor(hdcEdit, text_bg_brush ? text_bg_color
: (COLORREF) GetSysColor(
DEFAULT_COLOR_BG_TEXT));
SetTextColor(hdcEdit, text_fg_brush ? text_fg_color
: (COLORREF) GetSysColor(
DEFAULT_COLOR_FG_TEXT));
return (INT_PTR)(text_bg_brush
? text_bg_brush
: SYSCLR_TO_BRUSH(DEFAULT_COLOR_BG_TEXT));
}
}
return FALSE;
case WM_CTLCOLORDLG:
return (INT_PTR)(text_bg_brush
? text_bg_brush
: SYSCLR_TO_BRUSH(DEFAULT_COLOR_BG_TEXT));
case WM_DESTROY:
if (data) {
DeleteDC(data->bmpDC);
DeleteObject(data->bmpChecked);
DeleteObject(data->bmpCheckedCount);
DeleteObject(data->bmpNotChecked);
if (data->type == MENU_TYPE_TEXT) {
if (data->menui.text.text)
free(data->menui.text.text);
}
free(data);
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) 0);
}
return TRUE;
}
return FALSE;
}
/*-----------------------------------------------------------------------------*/
void
onMSNHCommand(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
PNHMenuWindow data;
data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
switch (wParam) {
case MSNH_MSG_PUTSTR: {
PMSNHMsgPutstr msg_data = (PMSNHMsgPutstr) lParam;
HWND text_view;
TCHAR wbuf[BUFSZ];
size_t text_size;
RECT text_rt;
HGDIOBJ saveFont;
HDC hdc;
if (data->type != MENU_TYPE_TEXT)
SetMenuType(hWnd, MENU_TYPE_TEXT);
if (!data->menui.text.text) {
text_size = strlen(msg_data->text) + 4;
data->menui.text.text =
(TCHAR *) malloc(text_size * sizeof(data->menui.text.text[0]));
ZeroMemory(data->menui.text.text,
text_size * sizeof(data->menui.text.text[0]));
} else {
text_size = _tcslen(data->menui.text.text) + strlen(msg_data->text) + 4;
data->menui.text.text = (TCHAR *) realloc(
data->menui.text.text, text_size * sizeof(data->menui.text.text[0]));
}
if (!data->menui.text.text)
break;
_tcscat(data->menui.text.text, NH_A2W(msg_data->text, wbuf, BUFSZ));
_tcscat(data->menui.text.text, TEXT("\r\n"));
text_view = GetDlgItem(hWnd, IDC_MENU_TEXT);
if (!text_view)
panic("cannot get text view window");
SetWindowText(text_view, data->menui.text.text);
/* calculate dimensions of the added line of text */
hdc = GetDC(text_view);
cached_font * font = mswin_get_font(NHW_MENU, ATR_NONE, hdc, FALSE);
saveFont = SelectObject(hdc, font->hFont);
SetRect(&text_rt, 0, 0, 0, 0);
DrawTextA(hdc, msg_data->text, strlen(msg_data->text), &text_rt,
DT_CALCRECT | DT_TOP | DT_LEFT | DT_NOPREFIX
| DT_SINGLELINE);
data->menui.text.text_box_size.cx =
max(text_rt.right - text_rt.left, data->menui.text.text_box_size.cx);
data->menui.text.text_box_size.cy += text_rt.bottom - text_rt.top;
SelectObject(hdc, saveFont);
ReleaseDC(text_view, hdc);
} break;
case MSNH_MSG_STARTMENU: {
int i;
if (data->type != MENU_TYPE_MENU)
SetMenuType(hWnd, MENU_TYPE_MENU);
if (data->menui.menu.items)
free(data->menui.menu.items);
data->how = PICK_NONE;
data->menui.menu.items = NULL;
data->menui.menu.size = 0;
data->menui.menu.allocated = 0;
data->done = 0;
data->result = 0;
for (i = 0; i < NUMTABS; ++i)
data->menui.menu.tab_stop_size[i] = MIN_TABSTOP_SIZE;
} break;
case MSNH_MSG_ADDMENU: {
PMSNHMsgAddMenu msg_data = (PMSNHMsgAddMenu) lParam;
char *p, *p1;
int new_item;
HDC hDC;
int column;
HFONT saveFont;
LONG menuitemwidth = 0;
TEXTMETRIC tm;
if (data->type != MENU_TYPE_MENU)
break;
if (data->menui.menu.size == data->menui.menu.allocated) {
data->menui.menu.allocated += 10;
data->menui.menu.items = (PNHMenuItem) realloc(
data->menui.menu.items, data->menui.menu.allocated * sizeof(NHMenuItem));
}
new_item = data->menui.menu.size;
ZeroMemory(&data->menui.menu.items[new_item],
sizeof(data->menui.menu.items[new_item]));
data->menui.menu.items[new_item].glyphinfo = msg_data->glyphinfo;
data->menui.menu.items[new_item].identifier = *msg_data->identifier;
data->menui.menu.items[new_item].accelerator = msg_data->accelerator;
data->menui.menu.items[new_item].group_accel = msg_data->group_accel;
data->menui.menu.items[new_item].attr = msg_data->attr;
strncpy(data->menui.menu.items[new_item].str, msg_data->str,
NHMENU_STR_SIZE);
/* prevent & being interpreted as a mnemonic start */
strNsubst(data->menui.menu.items[new_item].str, "&", "&&", 0);
data->menui.menu.items[new_item].presel = msg_data->presel;
data->menui.menu.items[new_item].itemflags = msg_data->itemflags;
/* calculate tabstop size */
hDC = GetDC(hWnd);
cached_font * font = mswin_get_font(NHW_MENU, msg_data->attr, hDC, FALSE);
saveFont = SelectObject(hDC, font->hFont);
GetTextMetrics(hDC, &tm);
p1 = data->menui.menu.items[new_item].str;
p = strchr(data->menui.menu.items[new_item].str, '\t');
column = 0;
for (;;) {
TCHAR wbuf[BUFSZ];
RECT drawRect;
SetRect(&drawRect, 0, 0, 1, 1);
if (p != NULL)
*p = '\0'; /* for time being, view tab field as zstring */
DrawText(hDC, NH_A2W(p1, wbuf, BUFSZ), strlen(p1), &drawRect,
DT_CALCRECT | DT_LEFT | DT_VCENTER | DT_EXPANDTABS
| DT_SINGLELINE);
data->menui.menu.tab_stop_size[column] =
max(data->menui.menu.tab_stop_size[column],
drawRect.right - drawRect.left);
menuitemwidth += data->menui.menu.tab_stop_size[column];
if (p != NULL)
*p = '\t';
else /* last string so, */
break;
/* add the separation only when not the last item */
/* in the last item, we break out of the loop, in the statement
* just above */
menuitemwidth += TAB_SEPARATION;
++column;
p1 = p + 1;
p = strchr(p1, '\t');
}
SelectObject(hDC, saveFont);
ReleaseDC(hWnd, hDC);
/* calculate new menu width */
data->menui.menu.menu_cx =
max(data->menui.menu.menu_cx,
2 * TILE_X + menuitemwidth
+ (tm.tmAveCharWidth + tm.tmOverhang) * 12);
/* increment size */
data->menui.menu.size++;
} break;
case MSNH_MSG_ENDMENU: {
PMSNHMsgEndMenu msg_data = (PMSNHMsgEndMenu) lParam;
if (msg_data->text) {
strncpy(data->menui.menu.prompt, msg_data->text,
sizeof(data->menui.menu.prompt) - 1);
} else {
ZeroMemory(data->menui.menu.prompt, sizeof(data->menui.menu.prompt));
}
} break;
case MSNH_MSG_RANDOM_INPUT: {
PostMessage(GetMenuControl(hWnd),
WM_MSNH_COMMAND, MSNH_MSG_RANDOM_INPUT, 0);
} break;
}
}
/*-----------------------------------------------------------------------------*/
void
LayoutMenu(HWND hWnd)
{
PNHMenuWindow data;
HWND menu_ok;
HWND menu_cancel;
RECT clrt, rt;
POINT pt_elem, pt_ok, pt_cancel;
SIZE sz_elem, sz_ok, sz_cancel;
data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
menu_ok = GetDlgItem(hWnd, IDOK);
menu_cancel = GetDlgItem(hWnd, IDCANCEL);
/* get window coordinates */
GetClientRect(hWnd, &clrt);
// OK button
if (data->is_active) {
GetWindowRect(menu_ok, &rt);
if (data->type == MENU_TYPE_TEXT
|| (data->type == MENU_TYPE_MENU && data->how == PICK_NONE)) {
sz_ok.cx = (clrt.right - clrt.left) - 2 * MENU_MARGIN;
} else {
sz_ok.cx = (clrt.right - clrt.left) / 2 - 2 * MENU_MARGIN;
}
sz_ok.cy = rt.bottom - rt.top;
pt_ok.x = clrt.left + MENU_MARGIN;
pt_ok.y = clrt.bottom - MENU_MARGIN - sz_ok.cy;
ShowWindow(menu_ok, SW_SHOW);
MoveWindow(menu_ok, pt_ok.x, pt_ok.y, sz_ok.cx, sz_ok.cy, TRUE);
} else {
sz_ok.cx = sz_ok.cy = 0;
pt_ok.x = pt_ok.y = 0;
ShowWindow(menu_ok, SW_HIDE);
}
// CANCEL button
if (data->is_active
&& !(data->type == MENU_TYPE_TEXT
|| (data->type == MENU_TYPE_MENU && data->how == PICK_NONE))) {
GetWindowRect(menu_ok, &rt);
sz_cancel.cx = (clrt.right - clrt.left) / 2 - 2 * MENU_MARGIN;
pt_cancel.x = (clrt.left + clrt.right) / 2 + MENU_MARGIN;
sz_cancel.cy = rt.bottom - rt.top;
pt_cancel.y = clrt.bottom - MENU_MARGIN - sz_cancel.cy;
ShowWindow(menu_cancel, SW_SHOW);
MoveWindow(menu_cancel, pt_cancel.x, pt_cancel.y, sz_cancel.cx,
sz_cancel.cy, TRUE);
} else {
sz_cancel.cx = sz_cancel.cy = 0;
pt_cancel.x = pt_cancel.y = 0;
ShowWindow(menu_cancel, SW_HIDE);
}
// main menu control
pt_elem.x = clrt.left + MENU_MARGIN;
pt_elem.y = clrt.top + MENU_MARGIN;
sz_elem.cx = (clrt.right - clrt.left) - 2 * MENU_MARGIN;
if (data->is_active) {
sz_elem.cy = (clrt.bottom - clrt.top) - max(sz_ok.cy, sz_cancel.cy)
- 3 * MENU_MARGIN;
} else {
sz_elem.cy = (clrt.bottom - clrt.top) - 2 * MENU_MARGIN;
}
if (data->type == MENU_TYPE_MENU) {
ListView_SetColumnWidth(
GetMenuControl(hWnd), 0,
max(clrt.right - clrt.left - GetSystemMetrics(SM_CXVSCROLL),
data->menui.menu.menu_cx));
}
MoveWindow(GetMenuControl(hWnd), pt_elem.x, pt_elem.y, sz_elem.cx,
sz_elem.cy, TRUE);
}
/*-----------------------------------------------------------------------------*/
void
SetMenuType(HWND hWnd, int type)
{
PNHMenuWindow data;
HWND list, text;
data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
data->type = type;
text = GetDlgItem(hWnd, IDC_MENU_TEXT);
list = GetDlgItem(hWnd, IDC_MENU_LIST);
if (data->type == MENU_TYPE_TEXT) {
ShowWindow(list, SW_HIDE);
EnableWindow(list, FALSE);
EnableWindow(text, TRUE);
ShowWindow(text, SW_SHOW);
if (data->is_active)
SetFocus(text);
} else {
ShowWindow(text, SW_HIDE);
EnableWindow(text, FALSE);
EnableWindow(list, TRUE);
ShowWindow(list, SW_SHOW);
if (data->is_active)
SetFocus(list);
}
LayoutMenu(hWnd);
}
/*-----------------------------------------------------------------------------*/
void
SetMenuListType(HWND hWnd, int how)
{
PNHMenuWindow data;
RECT rt;
DWORD dwStyles;
char buf[BUFSZ];
TCHAR wbuf[BUFSZ];
int nItem;
int i;
HWND control;
LVCOLUMN lvcol;
LRESULT fnt;
data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
if (data->type != MENU_TYPE_MENU)
return;
data->how = how;
switch (how) {
case PICK_NONE:
dwStyles = WS_VISIBLE | WS_TABSTOP | WS_CHILD | WS_VSCROLL
| WS_HSCROLL | LVS_REPORT | LVS_OWNERDRAWFIXED
| LVS_SINGLESEL;
break;
case PICK_ONE:
dwStyles = WS_VISIBLE | WS_TABSTOP | WS_CHILD | WS_VSCROLL
| WS_HSCROLL | LVS_REPORT | LVS_OWNERDRAWFIXED
| LVS_SINGLESEL;
break;
case PICK_ANY:
dwStyles = WS_VISIBLE | WS_TABSTOP | WS_CHILD | WS_VSCROLL
| WS_HSCROLL | LVS_REPORT | LVS_OWNERDRAWFIXED
| LVS_SINGLESEL;
break;
default:
panic("how should be one of PICK_NONE, PICK_ONE or PICK_ANY");
};
if (strlen(data->menui.menu.prompt) == 0) {
dwStyles |= LVS_NOCOLUMNHEADER;
}
GetWindowRect(GetDlgItem(hWnd, IDC_MENU_LIST), &rt);
DestroyWindow(GetDlgItem(hWnd, IDC_MENU_LIST));
control = CreateWindow(WC_LISTVIEW, NULL, dwStyles, rt.left, rt.top,
rt.right - rt.left, rt.bottom - rt.top, hWnd,
(HMENU) IDC_MENU_LIST, GetNHApp()->hApp, NULL);
if (!control)
panic("cannot create menu control");
/* install the hook for the control window procedure */
wndProcListViewOrig = (WNDPROC) GetWindowLongPtr(control, GWLP_WNDPROC);
SetWindowLongPtr(control, GWLP_WNDPROC, (LONG_PTR) NHMenuListWndProc);
/* set control colors */
ListView_SetBkColor(control, menu_bg_brush ? menu_bg_color
: (COLORREF) GetSysColor(
DEFAULT_COLOR_BG_MENU));
ListView_SetTextBkColor(
control, menu_bg_brush ? menu_bg_color : (COLORREF) GetSysColor(
DEFAULT_COLOR_BG_MENU));
ListView_SetTextColor(
control, menu_fg_brush ? menu_fg_color : (COLORREF) GetSysColor(
DEFAULT_COLOR_FG_MENU));
/* set control font */
fnt = SendMessage(hWnd, WM_GETFONT, (WPARAM) 0, (LPARAM) 0);
SendMessage(control, WM_SETFONT, (WPARAM) fnt, (LPARAM) 0);
/* add column to the list view */
MonitorInfo monitorInfo;
win10_monitor_info(hWnd, &monitorInfo);
ZeroMemory(&lvcol, sizeof(lvcol));
lvcol.mask = LVCF_WIDTH | LVCF_TEXT;
lvcol.cx = monitorInfo.width;
lvcol.pszText = NH_A2W(data->menui.menu.prompt, wbuf, BUFSZ);
ListView_InsertColumn(control, 0, &lvcol);
/* add items to the list view */
for (i = 0; i < data->menui.menu.size; i++) {
LVITEM lvitem;
ZeroMemory(&lvitem, sizeof(lvitem));
sprintf(buf, "%c - %s", max(data->menui.menu.items[i].accelerator, ' '),
data->menui.menu.items[i].str);
lvitem.mask = LVIF_PARAM | LVIF_STATE | LVIF_TEXT;
lvitem.iItem = i;
lvitem.iSubItem = 0;
lvitem.state = data->menui.menu.items[i].presel ? LVIS_SELECTED : 0;
lvitem.pszText = NH_A2W(buf, wbuf, BUFSZ);
lvitem.lParam = (LPARAM) &data->menui.menu.items[i];
nItem = (int) SendMessage(control, LB_ADDSTRING, (WPARAM) 0,
(LPARAM) buf);
if (ListView_InsertItem(control, &lvitem) == -1) {
panic("cannot insert menu item");
}
}
if (data->is_active)
SetFocus(control);
}
/*-----------------------------------------------------------------------------*/
HWND
GetMenuControl(HWND hWnd)
{
PNHMenuWindow data;
data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
/* We may continue getting window messages after a window's WM_DESTROY is
called. We need to handle the case that USERDATA has been freed. */
if (data == NULL)
return NULL;
if (data->type == MENU_TYPE_TEXT) {
return GetDlgItem(hWnd, IDC_MENU_TEXT);
} else {
return GetDlgItem(hWnd, IDC_MENU_LIST);
}
}
/*-----------------------------------------------------------------------------*/
BOOL
onMeasureItem(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
LPMEASUREITEMSTRUCT lpmis;
TEXTMETRIC tm;
HGDIOBJ saveFont;
HDC hdc;
PNHMenuWindow data;
RECT list_rect;
int i;
UNREFERENCED_PARAMETER(wParam);
lpmis = (LPMEASUREITEMSTRUCT) lParam;
data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
GetClientRect(GetMenuControl(hWnd), &list_rect);
hdc = GetDC(GetMenuControl(hWnd));
cached_font * font = mswin_get_font(NHW_MENU, ATR_INVERSE, hdc, FALSE);
saveFont = SelectObject(hdc, font->hFont);
GetTextMetrics(hdc, &tm);
/* Set the height of the list box items to max height of the individual
* items */
for (i = 0; i < data->menui.menu.size; i++) {
if (NHMENU_HAS_GLYPH(data->menui.menu.items[i])
&& !IS_MAP_ASCII(iflags.wc_map_mode)) {
lpmis->itemHeight =
max(lpmis->itemHeight,
(UINT) max(tm.tmHeight, GetNHApp()->mapTile_Y) + 2);
} else {
lpmis->itemHeight =
max(lpmis->itemHeight, (UINT) max(tm.tmHeight, TILE_Y) + 2);
}
}
/* set width to the window width */
lpmis->itemWidth = list_rect.right - list_rect.left;
SelectObject(hdc, saveFont);
ReleaseDC(GetMenuControl(hWnd), hdc);
return TRUE;
}
/*-----------------------------------------------------------------------------*/
BOOL
onDrawItem(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
LPDRAWITEMSTRUCT lpdis;
PNHMenuItem item;
PNHMenuWindow data;
TEXTMETRIC tm;
HGDIOBJ saveFont;
HDC tileDC;
short ntile;
int t_x, t_y;
int x, y;
TCHAR wbuf[BUFSZ];
RECT drawRect;
COLORREF OldBg, OldFg, NewBg;
char *p, *p1;
int column;
int spacing = 0;
int color = NO_COLOR, attr;
boolean menucolr = FALSE;
double monitorScale = win10_monitor_scale(hWnd);
int tileXScaled = (int) (TILE_X * monitorScale);
int tileYScaled = (int) (TILE_Y * monitorScale);
UNREFERENCED_PARAMETER(wParam);
lpdis = (LPDRAWITEMSTRUCT) lParam;
/* If there are no list box items, skip this message. */
if (lpdis->itemID == -1)
return FALSE;
data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
item = &data->menui.menu.items[lpdis->itemID];
tileDC = CreateCompatibleDC(lpdis->hDC);
cached_font * font = mswin_get_font(NHW_MENU, item->attr, lpdis->hDC, FALSE);
saveFont = SelectObject(lpdis->hDC, font->hFont);
NewBg = menu_bg_brush ? menu_bg_color
: (COLORREF) GetSysColor(DEFAULT_COLOR_BG_MENU);
OldBg = SetBkColor(lpdis->hDC, NewBg);
OldFg = SetTextColor(lpdis->hDC,
menu_fg_brush
? menu_fg_color
: (COLORREF) GetSysColor(DEFAULT_COLOR_FG_MENU));
GetTextMetrics(lpdis->hDC, &tm);
spacing = tm.tmAveCharWidth;
/* set initial offset */
x = lpdis->rcItem.left + 1;
/* print check mark and letter */
if (NHMENU_IS_SELECTABLE(*item)) {
char buf[2];
if (data->how != PICK_NONE) {
HBITMAP bmpCheck;
HBITMAP bmpSaved;
switch (item->count) {
case -1:
bmpCheck = data->bmpChecked;
break;
case 0:
bmpCheck = data->bmpNotChecked;
break;
default:
bmpCheck = data->bmpCheckedCount;
break;
}
y = (lpdis->rcItem.bottom + lpdis->rcItem.top - tileYScaled) / 2;
bmpSaved = SelectBitmap(data->bmpDC, bmpCheck);
StretchBlt(lpdis->hDC, x, y, tileXScaled, tileYScaled,
data->bmpDC, 0, 0, CHECK_WIDTH, CHECK_HEIGHT, SRCCOPY);
SelectObject(data->bmpDC, bmpSaved);
}
x += tileXScaled + spacing;
if (item->accelerator != 0) {
buf[0] = item->accelerator;
buf[1] = '\x0';
if (iflags.use_menu_color
&& (menucolr = get_menu_coloring(item->str, &color, &attr))) {
cached_font * menu_font = mswin_get_font(NHW_MENU, attr, lpdis->hDC, FALSE);
SelectObject(lpdis->hDC, menu_font->hFont);
if (color != NO_COLOR)
SetTextColor(lpdis->hDC, nhcolor_to_RGB(color));
}
SetRect(&drawRect, x, lpdis->rcItem.top, lpdis->rcItem.right,
lpdis->rcItem.bottom);
DrawText(lpdis->hDC, NH_A2W(buf, wbuf, 2), 1, &drawRect,
DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
}
x += tm.tmAveCharWidth + tm.tmOverhang + spacing;
} else {
x += tileXScaled + tm.tmAveCharWidth + tm.tmOverhang + 2 * spacing;
}
/* print glyph if present */
if (NHMENU_HAS_GLYPH(*item)) {
if (!IS_MAP_ASCII(iflags.wc_map_mode)) {
HGDIOBJ saveBmp;
double monitorScale2;
nhUse(monitorScale2);
monitorScale2 = win10_monitor_scale(hWnd);
saveBmp = SelectObject(tileDC, GetNHApp()->bmpMapTiles);
ntile = item->glyphinfo.gm.tileidx;
t_x =
(ntile % GetNHApp()->mapTilesPerLine) * GetNHApp()->mapTile_X;
t_y =
(ntile / GetNHApp()->mapTilesPerLine) * GetNHApp()->mapTile_Y;
y = (lpdis->rcItem.bottom + lpdis->rcItem.top - tileYScaled) / 2;
if (GetNHApp()->bmpMapTiles == GetNHApp()->bmpTiles) {
/* using original nethack tiles - apply image transparently */
(*GetNHApp()->lpfnTransparentBlt)(lpdis->hDC, x, y,
tileXScaled, tileYScaled,
tileDC, t_x, t_y, TILE_X, TILE_Y,
TILE_BK_COLOR);
} else {
/* using custom tiles - simple blt */
StretchBlt(lpdis->hDC, x, y, tileXScaled, tileYScaled,
tileDC, t_x, t_y, GetNHApp()->mapTile_X, GetNHApp()->mapTile_Y, SRCCOPY);
}
SelectObject(tileDC, saveBmp);
x += tileXScaled;
} else {
const char *sel_ind;
switch (item->count) {
case -1:
sel_ind = "+";
break;
case 0:
sel_ind = "-";
break;
default:
sel_ind = "#";
break;
}
SetRect(&drawRect, x, lpdis->rcItem.top,
min(x + tm.tmAveCharWidth, lpdis->rcItem.right),
lpdis->rcItem.bottom);
DrawText(lpdis->hDC, NH_A2W(sel_ind, wbuf, BUFSZ), 1, &drawRect,
DT_CENTER | DT_VCENTER | DT_SINGLELINE);
x += tm.tmAveCharWidth;
}
} else {
/* no glyph - need to adjust so help window won't look to cramped */
x += tileXScaled;
}
x += spacing;
/* draw item text */
p1 = item->str;
p = strchr(item->str, '\t');
column = 0;
SetRect(&drawRect, x, lpdis->rcItem.top,
min(x + data->menui.menu.tab_stop_size[0], lpdis->rcItem.right),
lpdis->rcItem.bottom);
for (;;) {
TCHAR wbuf2[BUFSZ];
if (p != NULL)
*p = '\0'; /* for time being, view tab field as zstring */
DrawText(lpdis->hDC, NH_A2W(p1, wbuf2, BUFSZ), strlen(p1), &drawRect,
DT_LEFT | DT_VCENTER | DT_SINGLELINE);
if (p != NULL)
*p = '\t';
else /* last string so, */
break;
p1 = p + 1;
p = strchr(p1, '\t');
drawRect.left = drawRect.right + TAB_SEPARATION;
++column;
drawRect.right = min(drawRect.left + data->menui.menu.tab_stop_size[column],
lpdis->rcItem.right);
}
/* draw focused item */
if (item->has_focus || (NHMENU_IS_SELECTABLE(*item)
&& data->menui.menu.items[lpdis->itemID].count != -1)) {
RECT client_rt;
GetClientRect(lpdis->hwndItem, &client_rt);
client_rt.right = min(client_rt.right, lpdis->rcItem.right);
if (NHMENU_IS_SELECTABLE(*item)
&& data->menui.menu.items[lpdis->itemID].count != 0
&& item->glyphinfo.glyph != NO_GLYPH) {
if (data->menui.menu.items[lpdis->itemID].count == -1) {
_stprintf(wbuf, TEXT("Count: All"));
} else {
_stprintf(wbuf, TEXT("Count: %d"),
data->menui.menu.items[lpdis->itemID].count);
}
/* TOOD: add blinking for blink text */
cached_font * blink_font = mswin_get_font(NHW_MENU, ATR_BLINK, lpdis->hDC, FALSE);
SelectObject(lpdis->hDC, blink_font->hFont);
/* calculate text rectangle */
SetRect(&drawRect, client_rt.left, lpdis->rcItem.top,
client_rt.right, lpdis->rcItem.bottom);
DrawText(lpdis->hDC, wbuf, _tcslen(wbuf), &drawRect,
DT_CALCRECT | DT_RIGHT | DT_VCENTER | DT_SINGLELINE
| DT_NOPREFIX);
/* erase text rectangle */
drawRect.left =
max(client_rt.left + 1,
client_rt.right - (drawRect.right - drawRect.left) - 10);
drawRect.right = client_rt.right - 1;
drawRect.top = lpdis->rcItem.top;
drawRect.bottom = lpdis->rcItem.bottom;
FillRect(lpdis->hDC, &drawRect,
menu_bg_brush ? menu_bg_brush
: SYSCLR_TO_BRUSH(DEFAULT_COLOR_BG_MENU));
/* draw text */
DrawText(lpdis->hDC, wbuf, _tcslen(wbuf), &drawRect,
DT_RIGHT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
}
}
if (item->has_focus) {
/* draw focus rect */
RECT client_rt;
GetClientRect(lpdis->hwndItem, &client_rt);
SetRect(&drawRect, client_rt.left, lpdis->rcItem.top,
client_rt.left + ListView_GetColumnWidth(lpdis->hwndItem, 0),
lpdis->rcItem.bottom);
DrawFocusRect(lpdis->hDC, &drawRect);
}
SetTextColor(lpdis->hDC, OldFg);
SetBkColor(lpdis->hDC, OldBg);
SelectObject(lpdis->hDC, saveFont);
DeleteDC(tileDC);
return TRUE;
}
/*-----------------------------------------------------------------------------*/
BOOL
onListChar(HWND hWnd, HWND hwndList, WORD ch)
{
int i = 0;
PNHMenuWindow data;
int curIndex, topIndex, pageSize;
boolean is_accelerator = FALSE;
data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
is_accelerator = FALSE;
for (i = 0; i < data->menui.menu.size; i++) {
if (data->menui.menu.items[i].accelerator == ch) {
is_accelerator = TRUE;
break;
}
}
/* Don't use switch if input matched an accelerator. Sometimes
* accelerators can conflict with menu actions. For example, when
* engraving the extra choice of using fingers matches MENU_UNSELECT_ALL.
*/
if (is_accelerator)
goto accelerator;
switch (ch) {
case MENU_FIRST_PAGE:
i = 0;
ListView_SetItemState(hwndList, i, LVIS_FOCUSED, LVIS_FOCUSED);
ListView_EnsureVisible(hwndList, i, FALSE);
return -2;
case MENU_LAST_PAGE:
i = max(0, data->menui.menu.size - 1);
ListView_SetItemState(hwndList, i, LVIS_FOCUSED, LVIS_FOCUSED);
ListView_EnsureVisible(hwndList, i, FALSE);
return -2;
case MENU_NEXT_PAGE:
topIndex = ListView_GetTopIndex(hwndList);
pageSize = ListView_GetCountPerPage(hwndList);
curIndex = ListView_GetNextItem(hwndList, -1, LVNI_FOCUSED);
/* Focus down one page */
i = min(curIndex + pageSize, data->menui.menu.size - 1);
ListView_SetItemState(hwndList, i, LVIS_FOCUSED, LVIS_FOCUSED);
/* Scrollpos down one page */
i = min(topIndex + (2 * pageSize - 1), data->menui.menu.size - 1);
ListView_EnsureVisible(hwndList, i, FALSE);
return -2;
case MENU_PREVIOUS_PAGE:
topIndex = ListView_GetTopIndex(hwndList);
pageSize = ListView_GetCountPerPage(hwndList);
curIndex = ListView_GetNextItem(hwndList, -1, LVNI_FOCUSED);
/* Focus up one page */
i = max(curIndex - pageSize, 0);
ListView_SetItemState(hwndList, i, LVIS_FOCUSED, LVIS_FOCUSED);
/* Scrollpos up one page */
i = max(topIndex - pageSize, 0);
ListView_EnsureVisible(hwndList, i, FALSE);
break;
case MENU_SELECT_ALL:
if (data->how == PICK_ANY) {
reset_menu_count(hwndList, data);
for (i = 0; i < data->menui.menu.size; i++) {
SelectMenuItem(hwndList, data, i, -1);
}
return -2;
}
break;
case MENU_UNSELECT_ALL:
if (data->how == PICK_ANY) {
reset_menu_count(hwndList, data);
for (i = 0; i < data->menui.menu.size; i++) {
SelectMenuItem(hwndList, data, i, 0);
}
return -2;
}
break;
case MENU_INVERT_ALL:
if (data->how == PICK_ANY) {
reset_menu_count(hwndList, data);
for (i = 0; i < data->menui.menu.size; i++) {
if (menuitem_invert_test(0, data->menui.menu.items[i].itemflags,
NHMENU_IS_SELECTED(data->menui.menu.items[i])))
SelectMenuItem(hwndList, data, i,
NHMENU_IS_SELECTED(data->menui.menu.items[i]) ? 0
: -1);
}
return -2;
}
break;
case MENU_SELECT_PAGE:
if (data->how == PICK_ANY) {
int from, to;
reset_menu_count(hwndList, data);
topIndex = ListView_GetTopIndex(hwndList);
pageSize = ListView_GetCountPerPage(hwndList);
from = max(0, topIndex);
to = min(data->menui.menu.size, from + pageSize);
for (i = from; i < to; i++) {
SelectMenuItem(hwndList, data, i, -1);
}
return -2;
}
break;
case MENU_UNSELECT_PAGE:
if (data->how == PICK_ANY) {
int from, to;
reset_menu_count(hwndList, data);
topIndex = ListView_GetTopIndex(hwndList);
pageSize = ListView_GetCountPerPage(hwndList);
from = max(0, topIndex);
to = min(data->menui.menu.size, from + pageSize);
for (i = from; i < to; i++) {
SelectMenuItem(hwndList, data, i, 0);
}
return -2;
}
break;
case MENU_INVERT_PAGE:
if (data->how == PICK_ANY) {
int from, to;
reset_menu_count(hwndList, data);
topIndex = ListView_GetTopIndex(hwndList);
pageSize = ListView_GetCountPerPage(hwndList);
from = max(0, topIndex);
to = min(data->menui.menu.size, from + pageSize);
for (i = from; i < to; i++) {
if (menuitem_invert_test(0, data->menui.menu.items[i].itemflags,
NHMENU_IS_SELECTED(data->menui.menu.items[i])))
SelectMenuItem(hwndList, data, i,
NHMENU_IS_SELECTED(data->menui.menu.items[i]) ? 0
: -1);
}
return -2;
}
break;
case MENU_SEARCH:
if (data->how == PICK_ANY || data->how == PICK_ONE) {
char buf[BUFSZ];
reset_menu_count(hwndList, data);
if (mswin_getlin_window("Search for:", buf, BUFSZ) == IDCANCEL) {
strcpy(buf, "\033");
}
if (data->is_active)
SetFocus(hwndList); // set focus back to the list control
if (!*buf || *buf == '\033')
return -2;
for (i = 0; i < data->menui.menu.size; i++) {
if (NHMENU_IS_SELECTABLE(data->menui.menu.items[i])
&& strstr(data->menui.menu.items[i].str, buf)) {
if (data->how == PICK_ANY) {
SelectMenuItem(
hwndList, data, i,
NHMENU_IS_SELECTED(data->menui.menu.items[i]) ? 0 : -1);
} else if (data->how == PICK_ONE) {
SelectMenuItem(hwndList, data, i, -1);
ListView_SetItemState(hwndList, i, LVIS_FOCUSED,
LVIS_FOCUSED);
ListView_EnsureVisible(hwndList, i, FALSE);
break;
}
}
}
} else {
mswin_nhbell();
}
return -2;
case ' ': {
if (GetNHApp()->regNetHackMode) {
/* NetHack mode: Scroll down one page,
ends menu when on last page. */
SCROLLINFO si;
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
GetScrollInfo(hwndList, SB_VERT, &si);
if ((si.nPos + (int) si.nPage) > (si.nMax - si.nMin)) {
/* We're at the bottom: dismiss. */
data->done = 1;
data->result = 0;
return -2;
}
/* We're not at the bottom: page down. */
topIndex = ListView_GetTopIndex(hwndList);
pageSize = ListView_GetCountPerPage(hwndList);
curIndex = ListView_GetNextItem(hwndList, -1, LVNI_FOCUSED);
/* Focus down one page */
i = min(curIndex + pageSize, data->menui.menu.size - 1);
ListView_SetItemState(hwndList, i, LVIS_FOCUSED, LVIS_FOCUSED);
/* Scrollpos down one page */
i = min(topIndex + (2 * pageSize - 1), data->menui.menu.size - 1);
ListView_EnsureVisible(hwndList, i, FALSE);
return -2;
} else {
/* Windows mode: ends menu for PICK_ONE/PICK_NONE
select item for PICK_ANY */
if (data->how == PICK_ONE || data->how == PICK_NONE) {
data->done = 1;
data->result = 0;
return -2;
} else if (data->how == PICK_ANY) {
i = ListView_GetNextItem(hwndList, -1, LVNI_FOCUSED);
if (i >= 0) {
SelectMenuItem(
hwndList, data, i,
NHMENU_IS_SELECTED(data->menui.menu.items[i]) ? 0 : -1);
}
}
}
} break;
accelerator:
default:
if (strchr(data->menui.menu.gacc, ch)
&& !(ch == '0' && data->menui.menu.counting)) {
/* matched a group accelerator */
if (data->how == PICK_ANY || data->how == PICK_ONE) {
reset_menu_count(hwndList, data);
for (i = 0; i < data->menui.menu.size; i++) {
if (NHMENU_IS_SELECTABLE(data->menui.menu.items[i])
&& data->menui.menu.items[i].group_accel == ch) {
if (data->how == PICK_ANY) {
SelectMenuItem(
hwndList, data, i,
NHMENU_IS_SELECTED(data->menui.menu.items[i]) ? 0
: -1);
} else if (data->how == PICK_ONE) {
SelectMenuItem(hwndList, data, i, -1);
data->result = 0;
data->done = 1;
return -2;
}
}
}
return -2;
} else {
mswin_nhbell();
return -2;
}
}
if (isdigit((uchar) ch)) {
int count;
i = ListView_GetNextItem(hwndList, -1, LVNI_FOCUSED);
if (i >= 0) {
count = data->menui.menu.items[i].count;
if (count == -1)
count = 0;
count *= 10L;
count += (int) (ch - '0');
if (count != 0) /* ignore leading zeros */ {
data->menui.menu.counting = TRUE;
data->menui.menu.items[i].count = min(100000, count);
ListView_RedrawItems(hwndList, i,
i); /* update count mark */
}
}
return -2;
}
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
|| is_accelerator) {
if (data->how == PICK_ANY || data->how == PICK_ONE) {
topIndex = ListView_GetTopIndex(hwndList);
if( topIndex < 0 || topIndex > data->menui.menu.size ) break; // impossible?
int iter = topIndex;
do {
i = iter % data->menui.menu.size;
if (data->menui.menu.items[i].accelerator == ch) {
if (data->how == PICK_ANY) {
SelectMenuItem(
hwndList, data, i,
NHMENU_IS_SELECTED(data->menui.menu.items[i]) ? 0
: -1);
ListView_SetItemState(hwndList, i, LVIS_FOCUSED,
LVIS_FOCUSED);
ListView_EnsureVisible(hwndList, i, FALSE);
return -2;
} else if (data->how == PICK_ONE) {
SelectMenuItem(hwndList, data, i, -1);
data->result = 0;
data->done = 1;
return -2;
}
}
} while( (++iter % data->menui.menu.size) != topIndex );
}
}
break;
}
reset_menu_count(hwndList, data);
return -1;
}
/*-----------------------------------------------------------------------------*/
void
mswin_menu_window_size(HWND hWnd, LPSIZE sz)
{
HWND control;
PNHMenuWindow data;
RECT rt, wrt;
int extra_cx;
data = (PNHMenuWindow) GetWindowLongPtr(hWnd, GWLP_USERDATA);
if (data) {
control = GetMenuControl(hWnd);
/* get the control size */
GetClientRect(control, &rt);
sz->cx = rt.right - rt.left;
sz->cy = rt.bottom - rt.top;
/* calculate "extra" space around the control */
GetWindowRect(hWnd, &wrt);
extra_cx = (wrt.right - wrt.left) - sz->cx;
if (data->type == MENU_TYPE_MENU) {
sz->cx = data->menui.menu.menu_cx + GetSystemMetrics(SM_CXVSCROLL);
} else {
/* Use the width of the text box */
sz->cx = data->menui.text.text_box_size.cx
+ 2 * GetSystemMetrics(SM_CXVSCROLL);
}
sz->cx += extra_cx;
} else {
/* uninitilized window */
GetClientRect(hWnd, &rt);
sz->cx = rt.right - rt.left;
sz->cy = rt.bottom - rt.top;
}
}
/*-----------------------------------------------------------------------------*/
void
SelectMenuItem(HWND hwndList, PNHMenuWindow data, int item, int count)
{
int i;
if (item < 0 || item >= data->menui.menu.size)
return;
if (data->how == PICK_ONE && count != 0) {
for (i = 0; i < data->menui.menu.size; i++)
if (item != i && data->menui.menu.items[i].count != 0) {
data->menui.menu.items[i].count = 0;
ListView_RedrawItems(hwndList, i, i);
};
}
data->menui.menu.items[item].count = count;
ListView_RedrawItems(hwndList, item, item);
reset_menu_count(hwndList, data);
}
/*-----------------------------------------------------------------------------*/
void
reset_menu_count(HWND hwndList, PNHMenuWindow data)
{
int i;
data->menui.menu.counting = FALSE;
if (IsWindow(hwndList)) {
i = ListView_GetNextItem((hwndList), -1, LVNI_FOCUSED);
if (i >= 0)
ListView_RedrawItems(hwndList, i, i);
}
}
/*-----------------------------------------------------------------------------*/
/* List window Proc */
LRESULT CALLBACK
NHMenuListWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HWND hWndParent = GetParent(hWnd);
BOOL bUpdateFocusItem;
/* we will redraw focused item whenever horizontal scrolling occurs
since "Count: XXX" indicator is garbled by scrolling */
bUpdateFocusItem = FALSE;
switch (message) {
case WM_KEYDOWN:
if (wParam == VK_LEFT || wParam == VK_RIGHT)
bUpdateFocusItem = TRUE;
break;
case WM_CHAR: /* filter keyboard input for the control */
if (wParam > 0 && wParam < 256
&& onListChar(GetParent(hWnd), hWnd, (char) wParam) == -2) {
return 0;
} else {
return 1;
}
break;
case WM_SIZE:
case WM_HSCROLL:
bUpdateFocusItem = TRUE;
break;
case WM_SETFOCUS:
if (GetParent(hWnd) != GetNHApp()->hPopupWnd) {
SetFocus(GetNHApp()->hMainWnd);
}
return FALSE;
case WM_MSNH_COMMAND:
if (wParam == MSNH_MSG_RANDOM_INPUT) {
char c = randomkey();
if (c == '\n')
PostMessage(hWndParent, WM_COMMAND, MAKELONG(IDOK, 0), 0);
else if (c == '\033')
PostMessage(hWndParent, WM_COMMAND, MAKELONG(IDCANCEL, 0), 0);
else
PostMessage(hWnd, WM_CHAR, c, 0);
return 0;
}
break;
}
/* update focused item */
if (bUpdateFocusItem) {
int i;
RECT rt;
/* invalidate the focus rectangle */
i = ListView_GetNextItem(hWnd, -1, LVNI_FOCUSED);
if (i != -1) {
ListView_GetItemRect(hWnd, i, &rt, LVIR_BOUNDS);
InvalidateRect(hWnd, &rt, TRUE);
}
}
/* call ListView control window proc */
if (wndProcListViewOrig)
return CallWindowProc(wndProcListViewOrig, hWnd, message, wParam,
lParam);
else
return 0;
}
/*-----------------------------------------------------------------------------*/
/* Text control window proc - implements scrolling without a cursor */
LRESULT CALLBACK
NHMenuTextWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HWND hWndParent = GetParent(hWnd);
HDC hDC;
RECT rc;
switch (message) {
case WM_ERASEBKGND:
hDC = (HDC) wParam;
GetClientRect(hWnd, &rc);
FillRect(hDC, &rc, text_bg_brush
? text_bg_brush
: SYSCLR_TO_BRUSH(DEFAULT_COLOR_BG_TEXT));
return 0;
case WM_KEYDOWN:
switch (wParam) {
/* close on space in Windows mode
page down on space in NetHack mode */
case VK_SPACE: {
SCROLLINFO si;
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
GetScrollInfo(hWnd, SB_VERT, &si);
/* If nethackmode and not at the end of the list */
if (GetNHApp()->regNetHackMode
&& (si.nPos + (int) si.nPage) <= (si.nMax - si.nMin))
SendMessage(hWnd, EM_SCROLL, SB_PAGEDOWN, 0);
else
PostMessage(hWndParent, WM_COMMAND, MAKELONG(IDOK, 0), 0);
return 0;
}
case VK_NEXT:
SendMessage(hWnd, EM_SCROLL, SB_PAGEDOWN, 0);
return 0;
case VK_PRIOR:
SendMessage(hWnd, EM_SCROLL, SB_PAGEUP, 0);
return 0;
case VK_UP:
SendMessage(hWnd, EM_SCROLL, SB_LINEUP, 0);
return 0;
case VK_DOWN:
SendMessage(hWnd, EM_SCROLL, SB_LINEDOWN, 0);
return 0;
}
break;
case WM_CHAR:
switch(wParam) {
case MENU_FIRST_PAGE:
SendMessage(hWnd, EM_SCROLL, SB_TOP, 0);
return 0;
case MENU_LAST_PAGE:
SendMessage(hWnd, EM_SCROLL, SB_BOTTOM, 0);
return 0;
case MENU_NEXT_PAGE:
SendMessage(hWnd, EM_SCROLL, SB_PAGEDOWN, 0);
return 0;
case MENU_PREVIOUS_PAGE:
SendMessage(hWnd, EM_SCROLL, SB_PAGEUP, 0);
return 0;
}
break;
/* edit control needs to know nothing of its focus */
case WM_SETFOCUS:
HideCaret(hWnd);
return 0;
case WM_MSNH_COMMAND:
if (wParam == MSNH_MSG_RANDOM_INPUT) {
char c = randomkey();
if (c == '\n')
PostMessage(hWndParent, WM_COMMAND, MAKELONG(IDOK, 0), 0);
else if (c == '\033')
PostMessage(hWndParent, WM_COMMAND, MAKELONG(IDCANCEL, 0), 0);
else
PostMessage(hWnd, WM_CHAR, c, 0);
return 0;
}
break;
}
if (editControlWndProc)
return CallWindowProc(editControlWndProc, hWnd, message, wParam,
lParam);
else
return 0;
}
/*-----------------------------------------------------------------------------*/