1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00

Windows: refactor config-box creation code.

I'm about to want to create a second entirely different dialog box
whose contents are described using the same dialog.h API as the main
config box. So I'm starting by moving as much handler code as possible
out of GenericMainDlgProc and its callers, and into a set of reusable
subroutines.

In particular, this gets rid of the disgusting static variables that
stored all the config-box state. Now they're stored in a more sensible
struct, which lives in the new context-pointer field provided by the
reworked ShinyDialogBox.
This commit is contained in:
Simon Tatham 2022-04-23 15:01:29 +01:00
parent cccdab9ba6
commit 1bd2af1f87

View File

@ -27,21 +27,190 @@
#define ICON_BIG 1
#endif
typedef struct PortableDialogStuff {
/*
* These are the various bits of data required to handle a dialog
* box that's been built up from the cross-platform dialog.c
* system.
*/
/* The 'controlbox' that was returned from the portable setup function */
struct controlbox *ctrlbox;
/* The dlgparam that's passed to all the runtime dlg_* functions.
* Declared as an array of 1 so it's convenient to pass it as a pointer. */
struct dlgparam dp[1];
/*
* Collections of instantiated controls. There can be more than
* one of these, because sometimes you want to destroy and
* recreate a subset of them - e.g. when switching panes in the
* main PuTTY config box, you delete and recreate _most_ of the
* controls, but not the OK and Cancel buttons at the bottom.
*/
size_t nctrltrees;
struct winctrls *ctrltrees;
/*
* Flag indicating whether the dialog box has been initialised.
* Used to suppresss spurious firing of message handlers during
* setup.
*/
bool initialised;
} PortableDialogStuff;
/*
* These are the various bits of data required to handle the
* portable-dialog stuff in the config box. Having them at file
* scope in here isn't too bad a place to put them; if we were ever
* to need more than one config box per process we could always
* shift them to a per-config-box structure stored in GWL_USERDATA.
* Initialise a PortableDialogStuff, before launching the dialog box.
*/
static struct controlbox *ctrlbox;
static PortableDialogStuff *pds_new(size_t nctrltrees)
{
PortableDialogStuff *pds = snew(PortableDialogStuff);
memset(pds, 0, sizeof(*pds));
pds->ctrlbox = ctrl_new_box();
dp_init(pds->dp);
pds->nctrltrees = nctrltrees;
pds->ctrltrees = snewn(pds->nctrltrees, struct winctrls);
for (size_t i = 0; i < pds->nctrltrees; i++) {
winctrl_init(&pds->ctrltrees[i]);
dp_add_tree(pds->dp, &pds->ctrltrees[i]);
}
pds->dp->errtitle = dupprintf("%s Error", appname);
pds->initialised = false;
return pds;
}
static void pds_free(PortableDialogStuff *pds)
{
ctrl_free_box(pds->ctrlbox);
dp_cleanup(pds->dp);
for (size_t i = 0; i < pds->nctrltrees; i++)
winctrl_cleanup(&pds->ctrltrees[i]);
sfree(pds->ctrltrees);
sfree(pds);
}
static INT_PTR pds_default_dlgproc(PortableDialogStuff *pds, HWND hwnd,
UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_LBUTTONUP:
/*
* Button release should trigger WM_OK if there was a
* previous double click on the host CA list.
*/
ReleaseCapture();
if (pds->dp->ended)
ShinyEndDialog(hwnd, pds->dp->endresult ? 1 : 0);
break;
case WM_COMMAND:
case WM_DRAWITEM:
default: { /* also handle drag list msg here */
/*
* Only process WM_COMMAND once the dialog is fully formed.
*/
int ret;
if (pds->initialised) {
ret = winctrl_handle_command(pds->dp, msg, wParam, lParam);
if (pds->dp->ended && GetCapture() != hwnd)
ShinyEndDialog(hwnd, pds->dp->endresult ? 1 : 0);
} else
ret = 0;
return ret;
}
case WM_HELP:
if (!winctrl_context_help(pds->dp,
hwnd, ((LPHELPINFO)lParam)->iCtrlId))
MessageBeep(0);
break;
case WM_CLOSE:
quit_help(hwnd);
ShinyEndDialog(hwnd, 0);
return 0;
/* Grrr Explorer will maximize Dialogs! */
case WM_SIZE:
if (wParam == SIZE_MAXIMIZED)
force_normal(hwnd);
return 0;
}
return 0;
}
static void pds_initdialog_start(PortableDialogStuff *pds, HWND hwnd)
{
pds->dp->hwnd = hwnd;
if (pds->dp->wintitle) /* apply override title, if provided */
SetWindowText(hwnd, pds->dp->wintitle);
/* The portable dialog system generally includes the ability to
* handle context help for particular controls. Enable the
* relevant window styles if we have a help file available. */
if (has_help()) {
LONG_PTR style = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
SetWindowLongPtr(hwnd, GWL_EXSTYLE, style | WS_EX_CONTEXTHELP);
} else {
/* If not, and if the dialog template provided a top-level
* Help button, delete it */
HWND item = GetDlgItem(hwnd, IDC_HELPBTN);
if (item)
DestroyWindow(item);
}
}
/*
* ctrls_base holds the OK and Cancel buttons: the controls which
* are present in all dialog panels. ctrls_panel holds the ones
* which change from panel to panel.
* Create the panelfuls of controls in the configuration box.
*/
static struct winctrls ctrls_base, ctrls_panel;
static struct dlgparam dp;
static void pds_create_controls(
PortableDialogStuff *pds, size_t which_tree, int base_id,
int left, int right, int top, char *path)
{
struct ctlpos cp;
ctlposinit(&cp, pds->dp->hwnd, left, right, top);
for (int index = -1; (index = ctrl_find_path(
pds->ctrlbox, path, index)) >= 0 ;) {
struct controlset *s = pds->ctrlbox->ctrlsets[index];
winctrl_layout(pds->dp, &pds->ctrltrees[which_tree], &cp, s, &base_id);
}
}
static void pds_initdialog_finish(PortableDialogStuff *pds)
{
/*
* Set focus into the first available control in ctrltree #0,
* which the caller was expected to set up to be the one
* containing the dialog controls likely to be used first.
*/
struct winctrl *c;
for (int i = 0; (c = winctrl_findbyindex(&pds->ctrltrees[0], i)) != NULL;
i++) {
if (c->ctrl) {
dlg_set_focus(c->ctrl, pds->dp);
break;
}
}
/*
* Now we've finished creating our initial set of controls,
* it's safe to actually show the window without risking setup
* flicker.
*/
ShowWindow(pds->dp->hwnd, SW_SHOWNORMAL);
pds->initialised = true;
}
#define LOGEVENT_INITIAL_MAX 128
#define LOGEVENT_CIRCULAR_MAX 128
@ -304,41 +473,14 @@ static HTREEITEM treeview_insert(struct treeview_faff *faff,
return newitem;
}
/*
* Create the panelfuls of controls in the configuration box.
*/
static void create_controls(HWND hwnd, char *path)
{
struct ctlpos cp;
int index;
int base_id;
struct winctrls *wc;
if (!path[0]) {
/*
* Here we must create the basic standard controls.
*/
ctlposinit(&cp, hwnd, 3, 3, 235);
wc = &ctrls_base;
base_id = IDCX_STDBASE;
} else {
/*
* Otherwise, we're creating the controls for a particular
* panel.
*/
ctlposinit(&cp, hwnd, 100, 3, 13);
wc = &ctrls_panel;
base_id = IDCX_PANELBASE;
}
for (index=-1; (index = ctrl_find_path(ctrlbox, path, index)) >= 0 ;) {
struct controlset *s = ctrlbox->ctrlsets[index];
winctrl_layout(&dp, wc, &cp, s, &base_id);
}
}
const char *dialog_box_demo_screenshot_filename = NULL;
/* ctrltrees indices for the main dialog box */
enum {
TREE_PANEL, /* things we swap out every time treeview selects a new pane */
TREE_BASE, /* fixed things at the bottom like OK and Cancel buttons */
};
/*
* This function is the configuration box.
* (Being a dialog procedure, in general it returns 0 if the default
@ -347,26 +489,17 @@ const char *dialog_box_demo_screenshot_filename = NULL;
static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
LPARAM lParam, void *ctx)
{
PortableDialogStuff *pds = (PortableDialogStuff *)ctx;
const int DEMO_SCREENSHOT_TIMER_ID = 1230;
HWND treeview;
struct treeview_faff tvfaff;
int ret;
switch (msg) {
case WM_INITDIALOG:
dp.hwnd = hwnd;
create_controls(hwnd, ""); /* Open and Cancel buttons etc */
SetWindowText(hwnd, dp.wintitle);
SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
if (has_help())
SetWindowLongPtr(hwnd, GWL_EXSTYLE,
GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
WS_EX_CONTEXTHELP);
else {
HWND item = GetDlgItem(hwnd, IDC_HELPBTN);
if (item)
DestroyWindow(item);
}
pds_initdialog_start(pds, hwnd);
pds_create_controls(pds, TREE_BASE, IDCX_STDBASE, 3, 3, 235, "");
SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
(LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON)));
@ -423,8 +556,8 @@ static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
char *path = NULL;
char *firstpath = NULL;
for (i = 0; i < ctrlbox->nctrlsets; i++) {
struct controlset *s = ctrlbox->ctrlsets[i];
for (i = 0; i < pds->ctrlbox->nctrlsets; i++) {
struct controlset *s = pds->ctrlbox->ctrlsets[i];
HTREEITEM item;
int j;
char *c;
@ -473,43 +606,17 @@ static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
* match the initial treeview selection.
*/
assert(firstpath); /* config.c must have given us _something_ */
create_controls(hwnd, firstpath);
dlg_refresh(NULL, &dp); /* and set up control values */
pds_create_controls(pds, TREE_PANEL, IDCX_PANELBASE,
100, 3, 13, firstpath);
dlg_refresh(NULL, pds->dp); /* and set up control values */
}
/*
* Set focus into the first available control.
*/
{
int i;
struct winctrl *c;
for (i = 0; (c = winctrl_findbyindex(&ctrls_panel, i)) != NULL;
i++) {
if (c->ctrl) {
dlg_set_focus(c->ctrl, &dp);
break;
}
}
}
/*
* Now we've finished creating our initial set of controls,
* it's safe to actually show the window without risking setup
* flicker.
*/
ShowWindow(hwnd, SW_SHOWNORMAL);
/*
* Set the flag that activates a couple of the other message
* handlers below, which were disabled until now to avoid
* spurious firing during the above setup procedure.
*/
SetWindowLongPtr(hwnd, GWLP_USERDATA, 1);
if (dialog_box_demo_screenshot_filename)
SetTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID, TICKSPERSEC, NULL);
pds_initdialog_finish(pds);
return 0;
case WM_TIMER:
if (dialog_box_demo_screenshot_filename &&
(UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) {
@ -522,15 +629,7 @@ static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
ShinyEndDialog(hwnd, 0);
}
return 0;
case WM_LBUTTONUP:
/*
* Button release should trigger WM_OK if there was a
* previous double click on the session list.
*/
ReleaseCapture();
if (dp.ended)
ShinyEndDialog(hwnd, dp.endresult ? 1 : 0);
break;
case WM_NOTIFY:
if (LOWORD(wParam) == IDCX_TREEVIEW &&
((LPNMHDR) lParam)->code == TVN_SELCHANGED) {
@ -545,7 +644,7 @@ static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
TVITEM item;
char buffer[64];
if (GetWindowLongPtr(hwnd, GWLP_USERDATA) != 1)
if (!pds->initialised)
return 0;
i = TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom);
@ -563,60 +662,34 @@ static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
HWND item;
struct winctrl *c;
while ((c = winctrl_findbyindex(&ctrls_panel, 0)) != NULL) {
while ((c = winctrl_findbyindex(
&pds->ctrltrees[TREE_PANEL], 0)) != NULL) {
for (k = 0; k < c->num_ids; k++) {
item = GetDlgItem(hwnd, c->base_id + k);
if (item)
DestroyWindow(item);
}
winctrl_rem_shortcuts(&dp, c);
winctrl_remove(&ctrls_panel, c);
winctrl_rem_shortcuts(pds->dp, c);
winctrl_remove(&pds->ctrltrees[TREE_PANEL], c);
sfree(c->data);
sfree(c);
}
}
create_controls(hwnd, (char *)item.lParam);
pds_create_controls(pds, TREE_PANEL, IDCX_PANELBASE,
100, 3, 13, (char *)item.lParam);
dlg_refresh(NULL, &dp); /* set up control values */
dlg_refresh(NULL, pds->dp); /* set up control values */
SendMessage (hwnd, WM_SETREDRAW, true, 0);
InvalidateRect (hwnd, NULL, true);
SetFocus(((LPNMHDR) lParam)->hwndFrom); /* ensure focus stays */
return 0;
}
break;
case WM_COMMAND:
case WM_DRAWITEM:
default: /* also handle drag list msg here */
/*
* Only process WM_COMMAND once the dialog is fully formed.
*/
if (GetWindowLongPtr(hwnd, GWLP_USERDATA) == 1) {
ret = winctrl_handle_command(&dp, msg, wParam, lParam);
if (dp.ended && GetCapture() != hwnd)
ShinyEndDialog(hwnd, dp.endresult ? 1 : 0);
} else
ret = 0;
return ret;
case WM_HELP:
if (!winctrl_context_help(&dp, hwnd,
((LPHELPINFO)lParam)->iCtrlId))
MessageBeep(0);
break;
case WM_CLOSE:
quit_help(hwnd);
ShinyEndDialog(hwnd, 0);
return 0;
/* Grrr Explorer will maximize Dialogs! */
case WM_SIZE:
if (wParam == SIZE_MAXIMIZED)
force_normal(hwnd);
return 0;
default:
return pds_default_dlgproc(pds, hwnd, msg, wParam, lParam);
}
return 0;
}
void modal_about_box(HWND hwnd)
@ -652,28 +725,22 @@ void defuse_showwindow(void)
bool do_config(Conf *conf)
{
bool ret;
PortableDialogStuff *pds = pds_new(2);
ctrlbox = ctrl_new_box();
setup_config_box(ctrlbox, false, 0, 0);
win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), false, 0);
dp_init(&dp);
winctrl_init(&ctrls_base);
winctrl_init(&ctrls_panel);
dp_add_tree(&dp, &ctrls_base);
dp_add_tree(&dp, &ctrls_panel);
dp.wintitle = dupprintf("%s Configuration", appname);
dp.errtitle = dupprintf("%s Error", appname);
dp.data = conf;
dlg_auto_set_fixed_pitch_flag(&dp);
dp.shortcuts['g'] = true; /* the treeview: `Cate&gory' */
setup_config_box(pds->ctrlbox, false, 0, 0);
win_setup_config_box(pds->ctrlbox, &pds->dp->hwnd, has_help(), false, 0);
pds->dp->wintitle = dupprintf("%s Configuration", appname);
pds->dp->data = conf;
dlg_auto_set_fixed_pitch_flag(pds->dp);
pds->dp->shortcuts['g'] = true; /* the treeview: `Cate&gory' */
ret = ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), "PuTTYConfigBox",
NULL, GenericMainDlgProc, NULL);
NULL, GenericMainDlgProc, pds);
ctrl_free_box(ctrlbox);
winctrl_cleanup(&ctrls_panel);
winctrl_cleanup(&ctrls_base);
dp_cleanup(&dp);
pds_free(pds);
return ret;
}
@ -683,31 +750,26 @@ bool do_reconfig(HWND hwnd, Conf *conf, int protcfginfo)
Conf *backup_conf;
bool ret;
int protocol;
PortableDialogStuff *pds = pds_new(2);
backup_conf = conf_copy(conf);
ctrlbox = ctrl_new_box();
protocol = conf_get_int(conf, CONF_protocol);
setup_config_box(ctrlbox, true, protocol, protcfginfo);
win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), true, protocol);
dp_init(&dp);
winctrl_init(&ctrls_base);
winctrl_init(&ctrls_panel);
dp_add_tree(&dp, &ctrls_base);
dp_add_tree(&dp, &ctrls_panel);
dp.wintitle = dupprintf("%s Reconfiguration", appname);
dp.errtitle = dupprintf("%s Error", appname);
dp.data = conf;
dlg_auto_set_fixed_pitch_flag(&dp);
dp.shortcuts['g'] = true; /* the treeview: `Cate&gory' */
setup_config_box(pds->ctrlbox, true, protocol, protcfginfo);
win_setup_config_box(pds->ctrlbox, &pds->dp->hwnd, has_help(),
true, protocol);
pds->dp->wintitle = dupprintf("%s Reconfiguration", appname);
pds->dp->data = conf;
dlg_auto_set_fixed_pitch_flag(pds->dp);
pds->dp->shortcuts['g'] = true; /* the treeview: `Cate&gory' */
ret = ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), "PuTTYConfigBox",
NULL, GenericMainDlgProc, NULL);
NULL, GenericMainDlgProc, pds);
ctrl_free_box(ctrlbox);
winctrl_cleanup(&ctrls_base);
winctrl_cleanup(&ctrls_panel);
dp_cleanup(&dp);
pds_free(pds);
if (!ret)
conf_copy_into(conf, backup_conf);