gcc has recognized various "magic comments" for white-listing
occurrences of implicit fallthrough in switch statements for
a long time:
The range and shape of "falls through" comments accepted are
contingent upon the level of the warning. (The default level is =3.)
-Wimplicit-fallthrough=0 disables the warning altogether.
-Wimplicit-fallthrough=1 treats any kind of comment as a "falls through" comment.
-Wimplicit-fallthrough=2 essentially accepts any comment that contains something
that matches (case insensitively) "falls?[ \t-]*thr(ough|u)" regular expression.
-Wimplicit-fallthrough=3 case sensitively matches a wide range of regular
expressions, listed in the GCC manual. E.g., all of these are accepted:
/* Falls through. */
/* fall-thru */
/* Else falls through. */
/* FALLTHRU */
/* ... falls through ... */
etc.
-Wimplicit-fallthrough=4 also, case sensitively matches a range of regular
expressions but is much more strict than level =3.
-Wimplicit-fallthrough=5 doesn't recognize any comments.
Plenty of other compilers did not recognize the gcc comment convention,
and up until now the compiler warning for detecting unintended
fallthrough had to be suppressed on other compilers. That's because the code
in NetHack has been relying on the gcc approach, and only the gcc approach.
The C23 standard introduces an attribute [[fallthrough]] for the
functionality, when implicit fallthrough warnings have been enabled.
Several popular compilers already support that, or a very similar attribute
style approach, today, even ahead of their C23 support:
C compiler whitelist approach
--------------------------- -------------------------------------
C23 conforming compilers [[fallthrough]]
clang versions supporting
standards prior to
C23 __attribute__((__fallthrough__))
Microsoft Visual Studio
since VS 2022 17.4.
The warning C5262 controls
whether the implict
fallthrough is detected and
warned about with
/std:clatest. [[fallthrough]]
This adds support to NetHack for the attribute approach by inserting a
macro FALLTHROUGH to the existing cases that require white-listing, so
other compilers can analyze things too.
The definition of the FALLTHROUGH macro is controlled in include/tradstdc.h.
The gcc comment approach has also been left in place at this time.
1049 lines
34 KiB
C
1049 lines
34 KiB
C
/* NetHack 3.7 mhdlg.c $NHDT-Date: 1596498347 2020/08/03 23:45:47 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.36 $ */
|
|
/* Copyright (C) 2001 by Alex Kompel */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
/* various dialog boxes are defined here */
|
|
|
|
#include "win10.h"
|
|
#include "winMS.h"
|
|
#include "hack.h"
|
|
#include "func_tab.h"
|
|
#include "resource.h"
|
|
#include "mhdlg.h"
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
/*---------------------------------------------------------------*/
|
|
/* data for getlin dialog */
|
|
struct getlin_data {
|
|
const char *question;
|
|
char *result;
|
|
size_t result_size;
|
|
};
|
|
|
|
INT_PTR CALLBACK GetlinDlgProc(HWND, UINT, WPARAM, LPARAM);
|
|
|
|
int
|
|
mswin_getlin_window(const char *question, char *result, size_t result_size)
|
|
{
|
|
if (iflags.debug_fuzzer) {
|
|
random_response(result, (int) result_size);
|
|
if (result[0] != '\0')
|
|
return IDOK;
|
|
else
|
|
return IDCANCEL;
|
|
}
|
|
|
|
INT_PTR ret;
|
|
struct getlin_data data;
|
|
|
|
/* initialize dialog data */
|
|
ZeroMemory(&data, sizeof(data));
|
|
data.question = question;
|
|
data.result = result;
|
|
data.result_size = result_size;
|
|
|
|
/* create modal dialog window */
|
|
ret = DialogBoxParam(GetNHApp()->hApp, MAKEINTRESOURCE(IDD_GETLIN),
|
|
GetNHApp()->hMainWnd, GetlinDlgProc, (LPARAM) &data);
|
|
if (ret == -1)
|
|
panic("Cannot create getlin window");
|
|
|
|
return (int) ret;
|
|
}
|
|
|
|
INT_PTR CALLBACK
|
|
GetlinDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
struct getlin_data *data;
|
|
RECT main_rt, dlg_rt;
|
|
SIZE dlg_sz;
|
|
TCHAR wbuf[BUFSZ];
|
|
HDC WindowDC;
|
|
HWND ControlHWND;
|
|
SIZE WindowExtents;
|
|
SIZE ViewPortExtents;
|
|
RECT ControlRect;
|
|
RECT ClientRect;
|
|
LONG Division;
|
|
LONG ButtonOffset;
|
|
|
|
switch (message) {
|
|
case WM_INITDIALOG:
|
|
data = (struct getlin_data *) lParam;
|
|
SetWindowText(hWnd, NH_A2W(data->question, wbuf, sizeof(wbuf)));
|
|
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) data);
|
|
|
|
/* center dialog in the main window */
|
|
GetWindowRect(hWnd, &dlg_rt);
|
|
GetWindowRect(GetNHApp()->hMainWnd, &main_rt);
|
|
WindowDC = GetWindowDC(hWnd);
|
|
|
|
if (!GetWindowExtEx(WindowDC, &WindowExtents)
|
|
|| !GetViewportExtEx(WindowDC, &ViewPortExtents)
|
|
|| !GetTextExtentPoint32(GetWindowDC(hWnd), wbuf, _tcslen(wbuf),
|
|
&dlg_sz)) {
|
|
dlg_sz.cx = 0;
|
|
} else {
|
|
/* I think we need to do the following scaling */
|
|
dlg_sz.cx *= ViewPortExtents.cx;
|
|
dlg_sz.cx /= WindowExtents.cx;
|
|
/* Add the size of the various items in the caption bar */
|
|
dlg_sz.cx += GetSystemMetrics(SM_CXSIZE)
|
|
+ 2 * (GetSystemMetrics(SM_CXBORDER)
|
|
+ GetSystemMetrics(SM_CXFRAME));
|
|
}
|
|
|
|
if (dlg_sz.cx < dlg_rt.right - dlg_rt.left)
|
|
dlg_sz.cx = dlg_rt.right - dlg_rt.left;
|
|
dlg_sz.cy = dlg_rt.bottom - dlg_rt.top;
|
|
dlg_rt.left = (main_rt.left + main_rt.right - dlg_sz.cx) / 2;
|
|
dlg_rt.right = dlg_rt.left + dlg_sz.cx;
|
|
dlg_rt.top = (main_rt.top + main_rt.bottom - dlg_sz.cy) / 2;
|
|
dlg_rt.bottom = dlg_rt.top + dlg_sz.cy;
|
|
MoveWindow(hWnd, (main_rt.left + main_rt.right - dlg_sz.cx) / 2,
|
|
(main_rt.top + main_rt.bottom - dlg_sz.cy) / 2, dlg_sz.cx,
|
|
dlg_sz.cy, TRUE);
|
|
|
|
/* set focus and size of the edit control */
|
|
ControlHWND = GetDlgItem(hWnd, IDC_GETLIN_EDIT);
|
|
SetFocus(ControlHWND);
|
|
GetClientRect(hWnd, &ClientRect);
|
|
GetWindowRect(ControlHWND, &ControlRect);
|
|
MoveWindow(ControlHWND, 0, 0, ClientRect.right - ClientRect.left,
|
|
ControlRect.bottom - ControlRect.top, TRUE);
|
|
ButtonOffset = ControlRect.bottom - ControlRect.top;
|
|
|
|
/* Now get the OK and CANCEL buttons */
|
|
ControlHWND = GetDlgItem(hWnd, IDOK);
|
|
GetWindowRect(ControlHWND, &ControlRect);
|
|
Division = ((ClientRect.right - ClientRect.left)
|
|
- 2 * (ControlRect.right - ControlRect.left)) / 3;
|
|
MoveWindow(ControlHWND, Division, ButtonOffset,
|
|
ControlRect.right - ControlRect.left,
|
|
ControlRect.bottom - ControlRect.top, TRUE);
|
|
ControlHWND = GetDlgItem(hWnd, IDCANCEL);
|
|
MoveWindow(ControlHWND,
|
|
Division * 2 + ControlRect.right - ControlRect.left,
|
|
ButtonOffset, ControlRect.right - ControlRect.left,
|
|
ControlRect.bottom - ControlRect.top, TRUE);
|
|
|
|
/* tell windows that we've set the focus */
|
|
return FALSE;
|
|
break;
|
|
|
|
case WM_COMMAND: {
|
|
TCHAR wbuf2[BUFSZ];
|
|
|
|
wbuf2[BUFSZ - 1] = '\0';
|
|
switch (LOWORD(wParam)) {
|
|
/* OK button was pressed */
|
|
case IDOK:
|
|
data =
|
|
(struct getlin_data *) GetWindowLongPtr(hWnd, GWLP_USERDATA);
|
|
SendDlgItemMessage(hWnd, IDC_GETLIN_EDIT, WM_GETTEXT,
|
|
(WPARAM) sizeof(wbuf2), (LPARAM) wbuf2);
|
|
NH_W2A(wbuf2, data->result, data->result_size);
|
|
|
|
FALLTHROUGH;
|
|
/* FALLTHRU */
|
|
|
|
/* cancel button was pressed */
|
|
case IDCANCEL:
|
|
EndDialog(hWnd, wParam);
|
|
return TRUE;
|
|
}
|
|
} break;
|
|
|
|
} /* end switch (message) */
|
|
return FALSE;
|
|
}
|
|
|
|
/*---------------------------------------------------------------*/
|
|
/* dialog data for the list of extended commands */
|
|
struct extcmd_data {
|
|
int *selection;
|
|
};
|
|
|
|
INT_PTR CALLBACK ExtCmdDlgProc(HWND, UINT, WPARAM, LPARAM);
|
|
|
|
int
|
|
mswin_ext_cmd_window(int *selection)
|
|
{
|
|
if (iflags.debug_fuzzer) {
|
|
*selection = rnd_extcmd_idx();
|
|
|
|
if (*selection != -1)
|
|
return IDOK;
|
|
else
|
|
return IDCANCEL;
|
|
}
|
|
|
|
INT_PTR ret;
|
|
struct extcmd_data data;
|
|
|
|
/* init dialog data */
|
|
ZeroMemory(&data, sizeof(data));
|
|
*selection = -1;
|
|
data.selection = selection;
|
|
|
|
/* create modal dialog window */
|
|
ret = DialogBoxParam(GetNHApp()->hApp, MAKEINTRESOURCE(IDD_EXTCMD),
|
|
GetNHApp()->hMainWnd, ExtCmdDlgProc, (LPARAM) &data);
|
|
if (ret == -1)
|
|
panic("Cannot create extcmd window");
|
|
return (int) ret;
|
|
}
|
|
|
|
INT_PTR CALLBACK
|
|
ExtCmdDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
struct extcmd_data *data;
|
|
RECT main_rt, dlg_rt;
|
|
SIZE dlg_sz;
|
|
int i;
|
|
TCHAR wbuf[255];
|
|
|
|
switch (message) {
|
|
case WM_INITDIALOG:
|
|
data = (struct extcmd_data *) lParam;
|
|
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) data);
|
|
|
|
/* center dialog in the main window */
|
|
GetWindowRect(GetNHApp()->hMainWnd, &main_rt);
|
|
GetWindowRect(hWnd, &dlg_rt);
|
|
dlg_sz.cx = dlg_rt.right - dlg_rt.left;
|
|
dlg_sz.cy = dlg_rt.bottom - dlg_rt.top;
|
|
|
|
dlg_rt.left = (main_rt.left + main_rt.right - dlg_sz.cx) / 2;
|
|
dlg_rt.right = dlg_rt.left + dlg_sz.cx;
|
|
dlg_rt.top = (main_rt.top + main_rt.bottom - dlg_sz.cy) / 2;
|
|
dlg_rt.bottom = dlg_rt.top + dlg_sz.cy;
|
|
MoveWindow(hWnd, (main_rt.left + main_rt.right - dlg_sz.cx) / 2,
|
|
(main_rt.top + main_rt.bottom - dlg_sz.cy) / 2, dlg_sz.cx,
|
|
dlg_sz.cy, TRUE);
|
|
|
|
/* fill combobox with extended commands */
|
|
for (i = 0; extcmdlist[i].ef_txt; i++) {
|
|
SendDlgItemMessage(
|
|
hWnd, IDC_EXTCMD_LIST, LB_ADDSTRING, (WPARAM) 0,
|
|
(LPARAM) NH_A2W(extcmdlist[i].ef_txt, wbuf, sizeof(wbuf)));
|
|
}
|
|
|
|
/* set focus to the list control */
|
|
SetFocus(GetDlgItem(hWnd, IDC_EXTCMD_LIST));
|
|
|
|
/* tell windows we set the focus */
|
|
return FALSE;
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
data = (struct extcmd_data *) GetWindowLongPtr(hWnd, GWLP_USERDATA);
|
|
switch (LOWORD(wParam)) {
|
|
/* OK button ws clicked */
|
|
case IDOK:
|
|
*data->selection = (int) SendDlgItemMessage(
|
|
hWnd, IDC_EXTCMD_LIST, LB_GETCURSEL, (WPARAM) 0, (LPARAM) 0);
|
|
if (*data->selection == LB_ERR)
|
|
*data->selection = -1;
|
|
FALLTHROUGH;
|
|
/* FALLTHRU */
|
|
|
|
/* CANCEL button ws clicked */
|
|
case IDCANCEL:
|
|
EndDialog(hWnd, wParam);
|
|
return TRUE;
|
|
|
|
/* list control events */
|
|
case IDC_EXTCMD_LIST:
|
|
switch (HIWORD(wParam)) {
|
|
case LBN_DBLCLK:
|
|
/* double click within the list
|
|
wParam
|
|
The low-order word is the list box identifier.
|
|
The high-order word is the notification message.
|
|
lParam
|
|
Handle to the list box
|
|
*/
|
|
*data->selection = (int) SendMessage(
|
|
(HWND) lParam, LB_GETCURSEL, (WPARAM) 0, (LPARAM) 0);
|
|
if (*data->selection == LB_ERR)
|
|
*data->selection = -1;
|
|
EndDialog(hWnd, IDOK);
|
|
return TRUE;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*---------------------------------------------------------------*/
|
|
/* player selector dialog */
|
|
|
|
/* NOTE: this enumeration is in control tab order */
|
|
enum player_selector_control {
|
|
psc_name_group,
|
|
psc_role_group,
|
|
psc_race_group,
|
|
psc_alignment_group,
|
|
psc_gender_group,
|
|
psc_name_box,
|
|
psc_role_list,
|
|
psc_race_list,
|
|
psc_lawful_button,
|
|
psc_neutral_button,
|
|
psc_chaotic_button,
|
|
psc_male_button,
|
|
psc_female_button,
|
|
psc_play_button,
|
|
psc_random_button,
|
|
psc_quit_button,
|
|
psc_control_count
|
|
};
|
|
|
|
static const int s_psc_id[psc_control_count] = {
|
|
IDC_PLSEL_NAME_GROUP,
|
|
IDC_PLSEL_ROLE_GROUP,
|
|
IDC_PLSEL_RACE_GROUP,
|
|
IDC_PLSEL_ALIGNMENT_GROUP,
|
|
IDC_PLSEL_GENDER_GROUP,
|
|
IDC_PLSEL_NAME,
|
|
IDC_PLSEL_ROLE_LIST,
|
|
IDC_PLSEL_RACE_LIST,
|
|
IDC_PLSEL_ALIGN_LAWFUL,
|
|
IDC_PLSEL_ALIGN_NEUTRAL,
|
|
IDC_PLSEL_ALIGN_CHAOTIC,
|
|
IDC_PLSEL_GENDER_MALE,
|
|
IDC_PLSEL_GENDER_FEMALE,
|
|
IDOK,
|
|
IDC_PLSEL_RANDOM,
|
|
IDCANCEL
|
|
};
|
|
|
|
typedef struct {
|
|
int id;
|
|
POINT pos;
|
|
SIZE size;
|
|
HWND hWnd;
|
|
} control_t;
|
|
|
|
typedef struct plsel_data {
|
|
HWND dialog;
|
|
HWND focus;
|
|
control_t controls[psc_control_count];
|
|
SIZE client_size;
|
|
int config_race;
|
|
int config_role;
|
|
int config_gender;
|
|
int config_alignment;
|
|
int role_count;
|
|
int race_count;
|
|
} plsel_data_t;
|
|
|
|
INT_PTR CALLBACK PlayerSelectorDlgProc(HWND, UINT, WPARAM, LPARAM);
|
|
static void plselAdjustSelections(HWND hWnd);
|
|
static boolean plselRandomize(plsel_data_t * data);
|
|
static BOOL plselDrawItem(HWND hWnd, WPARAM wParam, LPARAM lParam);
|
|
|
|
boolean
|
|
mswin_player_selection_window(void)
|
|
{
|
|
INT_PTR ret;
|
|
plsel_data_t data;
|
|
boolean ok = TRUE;
|
|
|
|
/* save away configuration settings */
|
|
data.config_role = flags.initrole;
|
|
data.config_race = flags.initrace;
|
|
data.config_gender = flags.initgend;
|
|
data.config_alignment = flags.initalign;
|
|
|
|
if (!plselRandomize(&data)) {
|
|
/* create modal dialog */
|
|
ret = DialogBoxParam(
|
|
GetNHApp()->hApp, MAKEINTRESOURCE(IDD_PLAYER_SELECTOR),
|
|
GetNHApp()->hMainWnd, PlayerSelectorDlgProc, (LPARAM) &data);
|
|
if (ret == -1)
|
|
panic("Cannot create getlin window");
|
|
ok = (ret == IDOK);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
int
|
|
list_view_height(HWND hWnd, int count)
|
|
{
|
|
return (ListView_ApproximateViewRect(hWnd, -1, -1, count)) >> 16;
|
|
}
|
|
|
|
/* calculate the size and position of the controls taking into account
|
|
the per-monitor DPI expressed as a scaling factor on sizes at 96 DPI */
|
|
void
|
|
calculate_player_selector_layout(plsel_data_t * data)
|
|
{
|
|
MonitorInfo monitorInfo;
|
|
win10_monitor_info(data->dialog, &monitorInfo);
|
|
|
|
double scale = monitorInfo.scale;
|
|
|
|
/* Note these hard coded sizes are in 96DPI pixels and must be
|
|
scaled by the per-monitor DPI scaling factor */
|
|
int list_width = (int) (120 * scale);
|
|
int group_border = (int) (16 * scale);
|
|
int client_border = (int) (16 * scale);
|
|
int group_spacing = (int) (16 * scale);
|
|
int button_width = (int) (80 * scale);
|
|
int button_height = (int) (28 * scale);
|
|
|
|
/* set control sizes */
|
|
control_t * name_box = &data->controls[psc_name_box];
|
|
name_box->size.cx = (int) (280 * scale);
|
|
name_box->size.cy = (int) (24 * scale);
|
|
|
|
control_t * role_list = &data->controls[psc_role_list];
|
|
/* NOTE: we don't scale the list view reported height as it appears these
|
|
values are the actual size the control will be drawn at using the
|
|
existing DPI value */
|
|
role_list->size.cy = list_view_height(role_list->hWnd, data->role_count);
|
|
role_list->size.cx = list_width;
|
|
|
|
control_t * race_list = &data->controls[psc_race_list];
|
|
race_list->size.cy = list_view_height(race_list->hWnd, data->race_count);
|
|
race_list->size.cx = list_width;
|
|
|
|
for(int i = psc_lawful_button; i <= psc_quit_button; i++) {
|
|
data->controls[i].size.cx = button_width;
|
|
data->controls[i].size.cy = button_height;
|
|
}
|
|
|
|
for(int i = 0; i < 3; i++) {
|
|
control_t * group_control = &data->controls[psc_name_group + i];
|
|
control_t * inner_control = &data->controls[psc_name_box + i];
|
|
group_control->size.cx = inner_control->size.cx + (2 * group_border);
|
|
group_control->size.cy = inner_control->size.cy + (2 * group_border);
|
|
}
|
|
|
|
control_t * alignment_group = &data->controls[psc_alignment_group];
|
|
alignment_group->size.cx = button_width + (2 * group_border);
|
|
alignment_group->size.cy = (3 * button_height) + (2 * group_border);
|
|
|
|
control_t * gender_group = &data->controls[psc_gender_group];
|
|
gender_group->size.cx = button_width + (2 * group_border);
|
|
gender_group->size.cy = (2 * button_height) + (2 * group_border);
|
|
|
|
/* set control positions */
|
|
control_t * name_group = &data->controls[psc_name_group];
|
|
name_group->pos.x = client_border;
|
|
name_group->pos.y = client_border;
|
|
|
|
control_t * role_group = &data->controls[psc_role_group];
|
|
role_group->pos.x = client_border;
|
|
role_group->pos.y = name_group->pos.y + name_group->size.cy + group_spacing;
|
|
|
|
control_t * race_group = &data->controls[psc_race_group];
|
|
race_group->pos.x = role_group->pos.x + role_group->size.cx + group_spacing;
|
|
race_group->pos.y = role_group->pos.y;
|
|
|
|
for(int i = 0; i < 3; i++) {
|
|
control_t * group_control = &data->controls[psc_name_group + i];
|
|
control_t * inner_control = &data->controls[psc_name_box + i];
|
|
inner_control->pos.x = group_control->pos.x + group_border;
|
|
inner_control->pos.y = group_control->pos.y + group_border;
|
|
}
|
|
|
|
alignment_group->pos.x = race_group->pos.x + race_group->size.cx + group_spacing;
|
|
alignment_group->pos.y = race_group->pos.y;
|
|
|
|
for(int i = psc_lawful_button; i <= psc_chaotic_button; i++) {
|
|
data->controls[i].pos.x = alignment_group->pos.x + group_border;
|
|
data->controls[i].pos.y = alignment_group->pos.y + group_border +
|
|
((i -psc_lawful_button) * button_height);
|
|
}
|
|
|
|
gender_group->pos.x = alignment_group->pos.x;
|
|
gender_group->pos.y = alignment_group->pos.y + alignment_group->size.cy + group_spacing;
|
|
|
|
for(int i = psc_male_button; i <= psc_female_button; i++) {
|
|
data->controls[i].pos.x = gender_group->pos.x + group_border;
|
|
data->controls[i].pos.y = gender_group->pos.y + group_border +
|
|
((i - psc_male_button) * button_height);
|
|
}
|
|
|
|
int group_bottom = role_group->pos.y + role_group->size.cy;
|
|
if (group_bottom < race_group->pos.y + race_group->size.cy)
|
|
group_bottom = race_group->pos.y + race_group->size.cy;
|
|
if (group_bottom < gender_group->pos.y + gender_group->size.cy)
|
|
group_bottom = gender_group->pos.y + gender_group->size.cy;
|
|
|
|
control_t * play_button = &data->controls[psc_play_button];
|
|
play_button->pos.y = group_bottom + group_spacing;
|
|
play_button->pos.x = role_group->pos.x;
|
|
|
|
control_t * random_button = &data->controls[psc_random_button];
|
|
random_button->pos.y = play_button->pos.y;
|
|
random_button->pos.x = race_list->pos.x;
|
|
|
|
control_t * quit_button = &data->controls[psc_quit_button];
|
|
quit_button->pos.y = play_button->pos.y;
|
|
quit_button->pos.x = data->controls[psc_female_button].pos.x;
|
|
|
|
data->client_size.cx = alignment_group->pos.x + alignment_group->size.cx;
|
|
data->client_size.cy = quit_button->pos.y + quit_button->size.cy;
|
|
data->client_size.cx += client_border;
|
|
data->client_size.cy += client_border;
|
|
}
|
|
|
|
void
|
|
get_rect_size(RECT * rect, SIZE * size)
|
|
{
|
|
size->cx = rect->right - rect->left + 1;
|
|
size->cy = rect->bottom - rect->top + 1;
|
|
}
|
|
|
|
/* center given dialog in the main window */
|
|
void
|
|
center_dialog(HWND dialog)
|
|
{
|
|
RECT main_rect;
|
|
SIZE main_size;
|
|
RECT dialog_rect;
|
|
SIZE dialog_size;
|
|
POINT pos;
|
|
|
|
GetWindowRect(GetNHApp()->hMainWnd, &main_rect);
|
|
get_rect_size(&main_rect, &main_size);
|
|
|
|
GetWindowRect(dialog, &dialog_rect);
|
|
get_rect_size(&dialog_rect, &dialog_size);
|
|
|
|
pos.x = main_rect.left + (main_size.cx - dialog_size.cx) / 2;
|
|
pos.y = main_rect.top + (main_size.cy - dialog_size.cy) / 2;
|
|
|
|
MoveWindow(dialog, pos.x, pos.y, dialog_size.cx, dialog_size.cy,
|
|
TRUE);
|
|
}
|
|
|
|
/* size the dialog such that it has the given client rect size */
|
|
void
|
|
size_dialog(HWND dialog, SIZE new_client_size)
|
|
{
|
|
RECT dialog_rect;
|
|
SIZE dialog_size;
|
|
RECT client_rect;
|
|
SIZE client_size;
|
|
|
|
GetWindowRect(dialog, &dialog_rect);
|
|
get_rect_size(&dialog_rect, &dialog_size);
|
|
|
|
GetClientRect(dialog, &client_rect);
|
|
get_rect_size(&client_rect, &client_size);
|
|
|
|
dialog_size.cx += new_client_size.cx - client_size.cx;
|
|
dialog_size.cy += new_client_size.cy - client_size.cy;
|
|
|
|
MoveWindow(dialog, dialog_rect.left, dialog_rect.top,
|
|
dialog_size.cx, dialog_size.cy, TRUE);
|
|
}
|
|
|
|
/* helper routine to move all controls according to there position
|
|
and size information */
|
|
void
|
|
move_controls(control_t * controls, int count)
|
|
{
|
|
control_t * control = controls;
|
|
while(count-- > 0) {
|
|
MoveWindow(control->hWnd, control->pos.x, control->pos.y,
|
|
control->size.cx, control->size.cy, TRUE);
|
|
control++;
|
|
}
|
|
}
|
|
|
|
/* adjust the size and positions of all controls in the player
|
|
selection dialog taking into account the per-monitor DPI. */
|
|
void
|
|
do_player_selector_layout(plsel_data_t * data)
|
|
{
|
|
calculate_player_selector_layout(data);
|
|
move_controls(data->controls, psc_control_count);
|
|
size_dialog(data->dialog, data->client_size);
|
|
}
|
|
|
|
/* initialize player selector dialog */
|
|
void
|
|
plselInitDialog(struct plsel_data * data)
|
|
{
|
|
TCHAR wbuf[BUFSZ];
|
|
LVCOLUMN lvcol;
|
|
control_t *control;
|
|
|
|
SetWindowLongPtr(data->dialog, GWLP_USERDATA, (LONG_PTR) data);
|
|
|
|
for(int i = 0; i < psc_control_count; i++) {
|
|
data->controls[i].id = s_psc_id[i];
|
|
data->controls[i].hWnd = GetDlgItem(data->dialog, s_psc_id[i]);
|
|
}
|
|
|
|
control_t * role_list = &data->controls[psc_role_list];
|
|
|
|
ZeroMemory(&lvcol, sizeof(lvcol));
|
|
lvcol.mask = LVCF_WIDTH;
|
|
lvcol.cx = 1024;
|
|
|
|
/* build role list */
|
|
ListView_InsertColumn(role_list->hWnd, 0, &lvcol);
|
|
data->role_count = 0;
|
|
for (int i = 0; roles[i].name.m; i++) {
|
|
LVITEM lvitem;
|
|
ZeroMemory(&lvitem, sizeof(lvitem));
|
|
|
|
lvitem.mask = LVIF_STATE | LVIF_TEXT;
|
|
lvitem.iItem = i;
|
|
lvitem.iSubItem = 0;
|
|
lvitem.state = 0;
|
|
lvitem.stateMask = LVIS_FOCUSED;
|
|
if (flags.female && roles[i].name.f)
|
|
lvitem.pszText = NH_A2W(roles[i].name.f, wbuf, BUFSZ);
|
|
else
|
|
lvitem.pszText = NH_A2W(roles[i].name.m, wbuf, BUFSZ);
|
|
if (ListView_InsertItem(role_list->hWnd, &lvitem) == -1) {
|
|
panic("cannot insert menu item");
|
|
}
|
|
data->role_count++;
|
|
}
|
|
|
|
/* build race list */
|
|
control_t * race_list = &data->controls[psc_race_list];
|
|
ListView_InsertColumn(race_list->hWnd, 0, &lvcol);
|
|
data->race_count = 0;
|
|
for (int i = 0; races[i].noun; i++) {
|
|
LVITEM lvitem;
|
|
ZeroMemory(&lvitem, sizeof(lvitem));
|
|
|
|
lvitem.mask = LVIF_STATE | LVIF_TEXT;
|
|
lvitem.iItem = i;
|
|
lvitem.iSubItem = 0;
|
|
lvitem.state = 0;
|
|
lvitem.stateMask = LVIS_FOCUSED;
|
|
lvitem.pszText = NH_A2W(races[i].noun, wbuf, BUFSZ);
|
|
if (ListView_InsertItem(race_list->hWnd, &lvitem) == -1) {
|
|
panic("cannot insert menu item");
|
|
}
|
|
data->race_count++;
|
|
}
|
|
|
|
/* set gender radio button state */
|
|
control_t * gender_buttons = &data->controls[psc_male_button];
|
|
for (int i = 0; i < ROLE_GENDERS; i++)
|
|
Button_Enable(gender_buttons[i].hWnd, TRUE);
|
|
|
|
Button_SetCheck(data->controls[psc_male_button].hWnd, BST_CHECKED);
|
|
|
|
/* set alignment radio button state */
|
|
control_t * alignment_buttons = &data->controls[psc_lawful_button];
|
|
for (int i = 0; i < ROLE_ALIGNS; i++)
|
|
Button_Enable(alignment_buttons[i].hWnd, TRUE);
|
|
|
|
Button_SetCheck(data->controls[psc_lawful_button].hWnd, BST_CHECKED);
|
|
|
|
/* set player name */
|
|
control_t * name_box = &data->controls[psc_name_box];
|
|
SetDlgItemText(data->dialog, name_box->id, NH_A2W(svp.plname, wbuf, sizeof(wbuf)));
|
|
|
|
plselRandomize(data);
|
|
|
|
/* populate select boxes */
|
|
plselAdjustSelections(data->dialog);
|
|
|
|
/* set tab order */
|
|
|
|
for (int i = psc_quit_button; i >= psc_name_box; i--) {
|
|
control = &data->controls[i];
|
|
SetWindowPos(control->hWnd, NULL, 0, 0, 0, 0,
|
|
SWP_NOMOVE | SWP_NOSIZE);
|
|
}
|
|
do_player_selector_layout(data);
|
|
|
|
center_dialog(data->dialog);
|
|
}
|
|
|
|
INT_PTR CALLBACK
|
|
PlayerSelectorDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
plsel_data_t *data;
|
|
|
|
switch (message) {
|
|
case WM_INITDIALOG:
|
|
|
|
data = (plsel_data_t *) lParam;
|
|
data->dialog = hWnd;
|
|
|
|
plselInitDialog(data);
|
|
|
|
/* tell windows to set the focus */
|
|
return TRUE;
|
|
|
|
case WM_DRAWITEM:
|
|
if (wParam == IDC_PLSEL_ROLE_LIST || wParam == IDC_PLSEL_RACE_LIST)
|
|
return plselDrawItem(hWnd, wParam, lParam);
|
|
break;
|
|
|
|
case WM_NOTIFY:
|
|
{
|
|
LPNMHDR nmhdr = (LPNMHDR)lParam;
|
|
HWND control = nmhdr->hwndFrom;
|
|
|
|
data = (struct plsel_data *) GetWindowLongPtr(hWnd, GWLP_USERDATA);
|
|
|
|
control_t * role_control = &data->controls[psc_role_list];
|
|
control_t * race_control = &data->controls[psc_race_list];
|
|
|
|
switch (nmhdr->code) {
|
|
case LVN_KEYDOWN:
|
|
{
|
|
LPNMLVKEYDOWN lpnmkeydown = (LPNMLVKEYDOWN) lParam;
|
|
|
|
if (lpnmkeydown->wVKey == ' ') {
|
|
if (control == role_control->hWnd) {
|
|
int i = ListView_GetNextItem(control, -1, LVNI_FOCUSED);
|
|
assert(i == -1 || ListView_GetNextItem(control, i, LVNI_FOCUSED) == -1);
|
|
flags.initrole = i;
|
|
plselAdjustSelections(hWnd);
|
|
} else if (control == race_control->hWnd) {
|
|
int i = ListView_GetNextItem(control, -1, LVNI_FOCUSED);
|
|
assert(i == -1 || ListView_GetNextItem(control, i, LVNI_FOCUSED) == -1);
|
|
if (ok_race(flags.initrole, i, ROLE_RANDOM, ROLE_RANDOM)) {
|
|
flags.initrace = i;
|
|
plselAdjustSelections(hWnd);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case NM_CLICK:
|
|
{
|
|
LPNMLISTVIEW lpnmitem = (LPNMLISTVIEW)lParam;
|
|
int i = lpnmitem->iItem;
|
|
if (i == -1)
|
|
return FALSE;
|
|
if (control == role_control->hWnd) {
|
|
flags.initrole = i;
|
|
plselAdjustSelections(hWnd);
|
|
} else if(control == race_control->hWnd) {
|
|
if (ok_race(flags.initrole, i, ROLE_RANDOM, ROLE_RANDOM)) {
|
|
flags.initrace = i;
|
|
plselAdjustSelections(hWnd);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case NM_KILLFOCUS:
|
|
{
|
|
if (data->focus == race_control->hWnd) {
|
|
data->focus = NULL;
|
|
ListView_RedrawItems(race_control->hWnd, 0, data->race_count - 1);
|
|
} else if (data->focus == role_control->hWnd) {
|
|
data->focus = NULL;
|
|
ListView_RedrawItems(role_control->hWnd, 0, data->role_count - 1);
|
|
}
|
|
}
|
|
break;
|
|
case NM_SETFOCUS:
|
|
{
|
|
data->focus = control;
|
|
|
|
if (control == race_control->hWnd) {
|
|
data->focus = control;
|
|
plselAdjustSelections(hWnd);
|
|
} else if (control == role_control->hWnd) {
|
|
data->focus = control;
|
|
plselAdjustSelections(hWnd);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
data = (struct plsel_data *) GetWindowLongPtr(hWnd, GWLP_USERDATA);
|
|
switch (LOWORD(wParam)) {
|
|
/* OK button was clicked */
|
|
case IDOK:
|
|
EndDialog(hWnd, wParam);
|
|
return TRUE;
|
|
|
|
/* CANCEL button was clicked */
|
|
case IDCANCEL:
|
|
EndDialog(hWnd, wParam);
|
|
return TRUE;
|
|
|
|
case IDC_PLSEL_RANDOM:
|
|
plselRandomize(data);
|
|
plselAdjustSelections(hWnd);
|
|
return TRUE;
|
|
|
|
case IDC_PLSEL_GENDER_MALE:
|
|
case IDC_PLSEL_GENDER_FEMALE:
|
|
if (HIWORD(wParam) == BN_CLICKED) {
|
|
int i = LOWORD(wParam) - IDC_PLSEL_GENDER_MALE;
|
|
if (ok_gend(flags.initrole, flags.initrace, i, ROLE_RANDOM)) {
|
|
flags.initgend = i;
|
|
plselAdjustSelections(hWnd);
|
|
}
|
|
}
|
|
break;
|
|
|
|
|
|
case IDC_PLSEL_ALIGN_LAWFUL:
|
|
case IDC_PLSEL_ALIGN_NEUTRAL:
|
|
case IDC_PLSEL_ALIGN_CHAOTIC:
|
|
if (HIWORD(wParam) == BN_CLICKED) {
|
|
int i = LOWORD(wParam) - IDC_PLSEL_ALIGN_LAWFUL;
|
|
if (ok_align(flags.initrole, flags.initrace, flags.initgend, i)) {
|
|
flags.initalign = i;
|
|
plselAdjustSelections(hWnd);
|
|
}
|
|
}
|
|
break;
|
|
|
|
}
|
|
break;
|
|
|
|
case WM_DPICHANGED:
|
|
{
|
|
data = (struct plsel_data *) GetWindowLongPtr(hWnd, GWLP_USERDATA);
|
|
|
|
do_player_selector_layout(data);
|
|
|
|
InvalidateRect(hWnd, NULL, TRUE);
|
|
} break;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
void
|
|
plselAdjustSelections(HWND hWnd)
|
|
{
|
|
struct plsel_data * data = (plsel_data_t *) GetWindowLongPtr(hWnd, GWLP_USERDATA);
|
|
|
|
control_t * role_control = &data->controls[psc_role_list];
|
|
control_t * race_control = &data->controls[psc_race_list];
|
|
|
|
if (!ok_race(flags.initrole, flags.initrace, ROLE_RANDOM, ROLE_RANDOM))
|
|
flags.initrace = pick_race(flags.initrole, ROLE_RANDOM, ROLE_RANDOM, ROLE_RANDOM);
|
|
|
|
if (!ok_gend(flags.initrole, flags.initrace, flags.initgend, ROLE_RANDOM))
|
|
flags.initgend = pick_gend(flags.initrole, flags.initrace, ROLE_RANDOM , ROLE_RANDOM);
|
|
|
|
if (!ok_align(flags.initrole, flags.initrace, flags.initgend, flags.initalign))
|
|
flags.initalign = pick_align(flags.initrole, flags.initrace, flags.initgend , ROLE_RANDOM);
|
|
|
|
ListView_RedrawItems(role_control->hWnd, 0, data->role_count - 1);
|
|
ListView_RedrawItems(race_control->hWnd, 0, data->race_count - 1);
|
|
|
|
/* set gender radio button state */
|
|
for (int i = 0; i < ROLE_GENDERS; i++) {
|
|
HWND button = data->controls[psc_male_button+i].hWnd;
|
|
BOOL enable = ok_gend(flags.initrole, flags.initrace, i, flags.initalign);
|
|
Button_Enable(button, enable);
|
|
LRESULT state = Button_GetCheck(button);
|
|
if (state == BST_CHECKED && flags.initgend != i)
|
|
Button_SetCheck(button, BST_UNCHECKED);
|
|
if (state == BST_UNCHECKED && flags.initgend == i)
|
|
Button_SetCheck(button, BST_CHECKED);
|
|
}
|
|
|
|
/* set alignment radio button state */
|
|
for (int i = 0; i < ROLE_ALIGNS; i++) {
|
|
HWND button = data->controls[psc_lawful_button+i].hWnd;
|
|
BOOL enable = ok_align(flags.initrole, flags.initrace, flags.initgend, i);
|
|
Button_Enable(button, enable);
|
|
LRESULT state = Button_GetCheck(button);
|
|
if (state == BST_CHECKED && flags.initalign != i)
|
|
Button_SetCheck(button, BST_UNCHECKED);
|
|
if (state == BST_UNCHECKED && flags.initalign == i)
|
|
Button_SetCheck(button, BST_CHECKED);
|
|
}
|
|
|
|
}
|
|
|
|
/* player made up his mind - get final selection here */
|
|
int
|
|
plselFinalSelection(HWND hWnd)
|
|
{
|
|
int role, race, gender, alignment;
|
|
|
|
nhUse(role);
|
|
nhUse(race);
|
|
nhUse(gender);
|
|
nhUse(alignment);
|
|
role = flags.initrole;
|
|
race = flags.initrace;
|
|
gender = flags.initgend;
|
|
alignment = flags.initalign;
|
|
assert(role != ROLE_RANDOM && role != ROLE_NONE);
|
|
assert(race != ROLE_RANDOM && race != ROLE_NONE);
|
|
assert(gender != ROLE_RANDOM && gender != ROLE_NONE);
|
|
assert(alignment != ROLE_RANDOM && alignment != ROLE_NONE);
|
|
assert(ok_role(role, race, gender, alignment));
|
|
assert(ok_race(role, race, gender, alignment));
|
|
assert(ok_gend(role, race, gender, alignment));
|
|
assert(ok_align(role, race, gender, alignment));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static boolean plselRandomize(plsel_data_t * data)
|
|
{
|
|
int role, race, gender, alignment;
|
|
boolean fully_specified = TRUE;
|
|
|
|
// restore back to configuration settings
|
|
flags.initrole = data->config_role;
|
|
flags.initrace = data->config_race;
|
|
flags.initgend = data->config_gender;
|
|
flags.initalign = data->config_alignment;
|
|
|
|
if (!flags.randomall) {
|
|
if(flags.initrole == ROLE_NONE || flags.initrace == ROLE_NONE
|
|
|| flags.initgend == ROLE_NONE || flags.initalign == ROLE_NONE)
|
|
fully_specified = FALSE;
|
|
}
|
|
|
|
if (flags.initrole == ROLE_NONE)
|
|
flags.initrole = ROLE_RANDOM;
|
|
if (flags.initrace == ROLE_NONE)
|
|
flags.initrace = ROLE_RANDOM;
|
|
if (flags.initgend == ROLE_NONE)
|
|
flags.initgend = ROLE_RANDOM;
|
|
if (flags.initalign == ROLE_NONE)
|
|
flags.initalign = ROLE_RANDOM;
|
|
|
|
rigid_role_checks();
|
|
|
|
role = flags.initrole;
|
|
race = flags.initrace;
|
|
gender = flags.initgend;
|
|
alignment = flags.initalign;
|
|
|
|
assert(role != ROLE_RANDOM && role != ROLE_NONE);
|
|
assert(race != ROLE_RANDOM && race != ROLE_NONE);
|
|
assert(gender != ROLE_RANDOM && gender != ROLE_NONE);
|
|
assert(alignment != ROLE_RANDOM && alignment != ROLE_NONE);
|
|
|
|
if (!ok_role(role, race, gender, alignment)) {
|
|
fully_specified = FALSE;
|
|
flags.initrole = ROLE_RANDOM;
|
|
}
|
|
|
|
if (!ok_race(role, race, gender, alignment)) {
|
|
fully_specified = FALSE;
|
|
flags.initrace = ROLE_RANDOM;
|
|
}
|
|
|
|
if (!ok_gend(role, race, gender, alignment)) {
|
|
fully_specified = FALSE;
|
|
flags.initgend = ROLE_RANDOM;
|
|
}
|
|
|
|
if(!ok_align(role, race, gender, alignment))
|
|
{
|
|
fully_specified = FALSE;
|
|
flags.initalign = ROLE_RANDOM;
|
|
}
|
|
|
|
rigid_role_checks();
|
|
|
|
role = flags.initrole;
|
|
race = flags.initrace;
|
|
gender = flags.initgend;
|
|
alignment = flags.initalign;
|
|
|
|
assert(role != ROLE_RANDOM && role != ROLE_NONE);
|
|
assert(race != ROLE_RANDOM && race != ROLE_NONE);
|
|
assert(gender != ROLE_RANDOM && gender != ROLE_NONE);
|
|
assert(alignment != ROLE_RANDOM && alignment != ROLE_NONE);
|
|
assert(ok_role(role, race, gender, alignment));
|
|
assert(ok_race(role, race, gender, alignment));
|
|
assert(ok_gend(role, race, gender, alignment));
|
|
assert(ok_align(role, race, gender, alignment));
|
|
|
|
return fully_specified;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------*/
|
|
BOOL
|
|
plselDrawItem(HWND hWnd, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT) lParam;
|
|
struct plsel_data * data = (plsel_data_t *) GetWindowLongPtr(hWnd, GWLP_USERDATA);
|
|
|
|
/* If there are no list box items, skip this message. */
|
|
if (lpdis->itemID < 0)
|
|
return FALSE;
|
|
|
|
HWND control = GetDlgItem(hWnd, (int) wParam);
|
|
int i = lpdis->itemID;
|
|
|
|
const char * string;
|
|
|
|
boolean ok = TRUE;
|
|
boolean selected;
|
|
|
|
if (wParam == IDC_PLSEL_ROLE_LIST) {
|
|
if (flags.female && roles[i].name.f)
|
|
string = roles[i].name.f;
|
|
else
|
|
string = roles[i].name.m;
|
|
selected = (flags.initrole == i);
|
|
} else {
|
|
assert(wParam == IDC_PLSEL_RACE_LIST);
|
|
ok = ok_race(flags.initrole, i, ROLE_RANDOM, ROLE_RANDOM);
|
|
string = races[i].noun;
|
|
selected = (flags.initrace == i);
|
|
}
|
|
|
|
SetBkMode(lpdis->hDC, OPAQUE);
|
|
HBRUSH brush;
|
|
if (selected) {
|
|
brush = CreateSolidBrush(RGB(0, 0, 0));
|
|
SetTextColor(lpdis->hDC, RGB(255, 255, 255));
|
|
SetBkColor(lpdis->hDC, RGB(0, 0, 0));
|
|
} else {
|
|
brush = CreateSolidBrush(RGB(255, 255, 255));
|
|
if (!ok)
|
|
SetTextColor(lpdis->hDC, RGB(220, 0, 0));
|
|
else
|
|
SetTextColor(lpdis->hDC, RGB(0, 0, 0));
|
|
SetBkColor(lpdis->hDC, RGB(255, 255, 255));
|
|
}
|
|
|
|
FillRect(lpdis->hDC, &lpdis->rcItem, brush);
|
|
RECT rect = lpdis->rcItem;
|
|
rect.left += 5;
|
|
DrawTextA(lpdis->hDC, string, strlen(string), &rect,
|
|
DT_LEFT | DT_SINGLELINE | DT_VCENTER);
|
|
|
|
if (data->focus == control) {
|
|
if (lpdis->itemState & LVIS_FOCUSED) {
|
|
/* draw focus rect */
|
|
RECT client_rt;
|
|
|
|
GetClientRect(lpdis->hwndItem, &client_rt);
|
|
SetRect(&rect, client_rt.left, lpdis->rcItem.top,
|
|
client_rt.left + ListView_GetColumnWidth(lpdis->hwndItem, 0),
|
|
lpdis->rcItem.bottom);
|
|
DrawFocusRect(lpdis->hDC, &rect);
|
|
}
|
|
}
|
|
|
|
DeleteObject(brush);
|
|
|
|
return TRUE;
|
|
}
|