1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 09:27:59 +00:00
putty-source/windows/controls.c
Simon Tatham f39c51f9a7 Rename most of the platform source files.
This gets rid of all those annoying 'win', 'ux' and 'gtk' prefixes
which made filenames annoying to type and to tab-complete. Also, as
with my other recent renaming sprees, I've taken the opportunity to
expand and clarify some of the names so that they're not such cryptic
abbreviations.
2021-04-26 18:00:01 +01:00

2601 lines
82 KiB
C

/*
* winctrls.c: routines to self-manage the controls in a dialog
* box.
*/
/*
* Possible TODO in new cross-platform config box stuff:
*
* - When lining up two controls alongside each other, I wonder if
* we could conveniently arrange to centre them vertically?
* Particularly ugly in the current setup is the `Add new
* forwarded port:' static next to the rather taller `Remove'
* button.
*/
#include <assert.h>
#include <ctype.h>
#include "putty.h"
#include "misc.h"
#include "dialog.h"
#include <commctrl.h>
#define GAPBETWEEN 3
#define GAPWITHIN 1
#define GAPXBOX 7
#define GAPYBOX 4
#define DLGWIDTH 168
#define STATICHEIGHT 8
#define TITLEHEIGHT 12
#define CHECKBOXHEIGHT 8
#define RADIOHEIGHT 8
#define EDITHEIGHT 12
#define LISTHEIGHT 11
#define LISTINCREMENT 8
#define COMBOHEIGHT 12
#define PUSHBTNHEIGHT 14
#define PROGBARHEIGHT 14
DECL_WINDOWS_FUNCTION(static, void, InitCommonControls, (void));
DECL_WINDOWS_FUNCTION(static, BOOL, MakeDragList, (HWND));
DECL_WINDOWS_FUNCTION(static, int, LBItemFromPt, (HWND, POINT, BOOL));
DECL_WINDOWS_FUNCTION(static, void, DrawInsert, (HWND, HWND, int));
void init_common_controls(void)
{
HMODULE comctl32_module = load_system32_dll("comctl32.dll");
GET_WINDOWS_FUNCTION(comctl32_module, InitCommonControls);
GET_WINDOWS_FUNCTION(comctl32_module, MakeDragList);
GET_WINDOWS_FUNCTION(comctl32_module, LBItemFromPt);
GET_WINDOWS_FUNCTION(comctl32_module, DrawInsert);
p_InitCommonControls();
}
void ctlposinit(struct ctlpos *cp, HWND hwnd,
int leftborder, int rightborder, int topborder)
{
RECT r, r2;
cp->hwnd = hwnd;
cp->font = SendMessage(hwnd, WM_GETFONT, 0, 0);
cp->ypos = topborder;
GetClientRect(hwnd, &r);
r2.left = r2.top = 0;
r2.right = 4;
r2.bottom = 8;
MapDialogRect(hwnd, &r2);
cp->dlu4inpix = r2.right;
cp->width = (r.right * 4) / (r2.right) - 2 * GAPBETWEEN;
cp->xoff = leftborder;
cp->width -= leftborder + rightborder;
}
HWND doctl(struct ctlpos *cp, RECT r,
char *wclass, int wstyle, int exstyle, char *wtext, int wid)
{
HWND ctl;
/*
* Note nonstandard use of RECT. This is deliberate: by
* transforming the width and height directly we arrange to
* have all supposedly same-sized controls really same-sized.
*/
r.left += cp->xoff;
MapDialogRect(cp->hwnd, &r);
/*
* We can pass in cp->hwnd == NULL, to indicate a dry run
* without creating any actual controls.
*/
if (cp->hwnd) {
ctl = CreateWindowEx(exstyle, wclass, wtext, wstyle,
r.left, r.top, r.right, r.bottom,
cp->hwnd, (HMENU)(ULONG_PTR)wid, hinst, NULL);
SendMessage(ctl, WM_SETFONT, cp->font, MAKELPARAM(true, 0));
if (!strcmp(wclass, "LISTBOX")) {
/*
* Bizarre Windows bug: the list box calculates its
* number of lines based on the font it has at creation
* time, but sending it WM_SETFONT doesn't cause it to
* recalculate. So now, _after_ we've sent it
* WM_SETFONT, we explicitly resize it (to the same
* size it was already!) to force it to reconsider.
*/
SetWindowPos(ctl, NULL, 0, 0, r.right, r.bottom,
SWP_NOACTIVATE | SWP_NOCOPYBITS |
SWP_NOMOVE | SWP_NOZORDER);
}
} else
ctl = NULL;
return ctl;
}
/*
* A title bar across the top of a sub-dialog.
*/
void bartitle(struct ctlpos *cp, char *name, int id)
{
RECT r;
r.left = GAPBETWEEN;
r.right = cp->width;
r.top = cp->ypos;
r.bottom = STATICHEIGHT;
cp->ypos += r.bottom + GAPBETWEEN;
doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, name, id);
}
/*
* Begin a grouping box, with or without a group title.
*/
void beginbox(struct ctlpos *cp, char *name, int idbox)
{
cp->boxystart = cp->ypos;
if (!name)
cp->boxystart -= STATICHEIGHT / 2;
if (name)
cp->ypos += STATICHEIGHT;
cp->ypos += GAPYBOX;
cp->width -= 2 * GAPXBOX;
cp->xoff += GAPXBOX;
cp->boxid = idbox;
cp->boxtext = name;
}
/*
* End a grouping box.
*/
void endbox(struct ctlpos *cp)
{
RECT r;
cp->xoff -= GAPXBOX;
cp->width += 2 * GAPXBOX;
cp->ypos += GAPYBOX - GAPBETWEEN;
r.left = GAPBETWEEN;
r.right = cp->width;
r.top = cp->boxystart;
r.bottom = cp->ypos - cp->boxystart;
doctl(cp, r, "BUTTON", BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 0,
cp->boxtext ? cp->boxtext : "", cp->boxid);
cp->ypos += GAPYBOX;
}
/*
* A static line, followed by a full-width edit box.
*/
void editboxfw(struct ctlpos *cp, bool password, char *text,
int staticid, int editid)
{
RECT r;
r.left = GAPBETWEEN;
r.right = cp->width;
if (text) {
r.top = cp->ypos;
r.bottom = STATICHEIGHT;
doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, staticid);
cp->ypos += STATICHEIGHT + GAPWITHIN;
}
r.top = cp->ypos;
r.bottom = EDITHEIGHT;
doctl(cp, r, "EDIT",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL |
(password ? ES_PASSWORD : 0),
WS_EX_CLIENTEDGE, "", editid);
cp->ypos += EDITHEIGHT + GAPBETWEEN;
}
/*
* A static line, followed by a full-width combo box.
*/
void combobox(struct ctlpos *cp, char *text, int staticid, int listid)
{
RECT r;
r.left = GAPBETWEEN;
r.right = cp->width;
if (text) {
r.top = cp->ypos;
r.bottom = STATICHEIGHT;
doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, staticid);
cp->ypos += STATICHEIGHT + GAPWITHIN;
}
r.top = cp->ypos;
r.bottom = COMBOHEIGHT * 10;
doctl(cp, r, "COMBOBOX",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
CBS_DROPDOWN | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", listid);
cp->ypos += COMBOHEIGHT + GAPBETWEEN;
}
struct radio { char *text; int id; };
static void radioline_common(struct ctlpos *cp, char *text, int id,
int nacross, struct radio *buttons, int nbuttons)
{
RECT r;
int group;
int i;
int j;
r.left = GAPBETWEEN;
r.top = cp->ypos;
if (text) {
r.right = cp->width;
r.bottom = STATICHEIGHT;
cp->ypos += r.bottom + GAPWITHIN;
doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, text, id);
} else {
r.right = r.bottom = 0;
}
group = WS_GROUP;
i = 0;
for (j = 0; j < nbuttons; j++) {
char *btext = buttons[j].text;
int bid = buttons[j].id;
if (i == nacross) {
cp->ypos += r.bottom + (nacross > 1 ? GAPBETWEEN : GAPWITHIN);
i = 0;
}
r.left = GAPBETWEEN + i * (cp->width + GAPBETWEEN) / nacross;
if (j < nbuttons-1)
r.right =
(i + 1) * (cp->width + GAPBETWEEN) / nacross - r.left;
else
r.right = cp->width - r.left;
r.top = cp->ypos;
r.bottom = RADIOHEIGHT;
doctl(cp, r, "BUTTON",
BS_NOTIFY | BS_AUTORADIOBUTTON | WS_CHILD |
WS_VISIBLE | WS_TABSTOP | group, 0, btext, bid);
group = 0;
i++;
}
cp->ypos += r.bottom + GAPBETWEEN;
}
/*
* A set of radio buttons on the same line, with a static above
* them. `nacross' dictates how many parts the line is divided into
* (you might want this not to equal the number of buttons if you
* needed to line up some 2s and some 3s to look good in the same
* panel).
*
* There's a bit of a hack in here to ensure that if nacross
* exceeds the actual number of buttons, the rightmost button
* really does get all the space right to the edge of the line, so
* you can do things like
*
* (*) Button1 (*) Button2 (*) ButtonWithReallyLongTitle
*/
void radioline(struct ctlpos *cp, char *text, int id, int nacross, ...)
{
va_list ap;
struct radio *buttons;
int i, nbuttons;
va_start(ap, nacross);
nbuttons = 0;
while (1) {
char *btext = va_arg(ap, char *);
if (!btext)
break;
(void) va_arg(ap, int); /* id */
nbuttons++;
}
va_end(ap);
buttons = snewn(nbuttons, struct radio);
va_start(ap, nacross);
for (i = 0; i < nbuttons; i++) {
buttons[i].text = va_arg(ap, char *);
buttons[i].id = va_arg(ap, int);
}
va_end(ap);
radioline_common(cp, text, id, nacross, buttons, nbuttons);
sfree(buttons);
}
/*
* A set of radio buttons on the same line, without a static above
* them. Otherwise just like radioline.
*/
void bareradioline(struct ctlpos *cp, int nacross, ...)
{
va_list ap;
struct radio *buttons;
int i, nbuttons;
va_start(ap, nacross);
nbuttons = 0;
while (1) {
char *btext = va_arg(ap, char *);
if (!btext)
break;
(void) va_arg(ap, int); /* id */
nbuttons++;
}
va_end(ap);
buttons = snewn(nbuttons, struct radio);
va_start(ap, nacross);
for (i = 0; i < nbuttons; i++) {
buttons[i].text = va_arg(ap, char *);
buttons[i].id = va_arg(ap, int);
}
va_end(ap);
radioline_common(cp, NULL, 0, nacross, buttons, nbuttons);
sfree(buttons);
}
/*
* A set of radio buttons on multiple lines, with a static above
* them.
*/
void radiobig(struct ctlpos *cp, char *text, int id, ...)
{
va_list ap;
struct radio *buttons;
int i, nbuttons;
va_start(ap, id);
nbuttons = 0;
while (1) {
char *btext = va_arg(ap, char *);
if (!btext)
break;
(void) va_arg(ap, int); /* id */
nbuttons++;
}
va_end(ap);
buttons = snewn(nbuttons, struct radio);
va_start(ap, id);
for (i = 0; i < nbuttons; i++) {
buttons[i].text = va_arg(ap, char *);
buttons[i].id = va_arg(ap, int);
}
va_end(ap);
radioline_common(cp, text, id, 1, buttons, nbuttons);
sfree(buttons);
}
/*
* A single standalone checkbox.
*/
void checkbox(struct ctlpos *cp, char *text, int id)
{
RECT r;
r.left = GAPBETWEEN;
r.top = cp->ypos;
r.right = cp->width;
r.bottom = CHECKBOXHEIGHT;
cp->ypos += r.bottom + GAPBETWEEN;
doctl(cp, r, "BUTTON",
BS_NOTIFY | BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0,
text, id);
}
/*
* Wrap a piece of text for a static text control. Returns the
* wrapped text (a malloc'ed string containing \ns), and also
* returns the number of lines required.
*/
char *staticwrap(struct ctlpos *cp, HWND hwnd, char *text, int *lines)
{
HDC hdc = GetDC(hwnd);
int width, nlines, j;
INT *pwidths, nfit;
SIZE size;
char *ret, *p, *q;
RECT r;
HFONT oldfont, newfont;
ret = snewn(1+strlen(text), char);
p = text;
q = ret;
pwidths = snewn(1+strlen(text), INT);
/*
* Work out the width the text will need to fit in, by doing
* the same adjustment that the `statictext' function itself
* will perform.
*/
SetMapMode(hdc, MM_TEXT); /* ensure logical units == pixels */
r.left = r.top = r.bottom = 0;
r.right = cp->width;
MapDialogRect(hwnd, &r);
width = r.right;
nlines = 1;
/*
* We must select the correct font into the HDC before calling
* GetTextExtent*, or silly things will happen.
*/
newfont = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0);
oldfont = SelectObject(hdc, newfont);
while (*p) {
if (!GetTextExtentExPoint(hdc, p, strlen(p), width,
&nfit, pwidths, &size) ||
(size_t)nfit >= strlen(p)) {
/*
* Either GetTextExtentExPoint returned failure, or the
* whole of the rest of the text fits on this line.
* Either way, we stop wrapping, copy the remainder of
* the input string unchanged to the output, and leave.
*/
strcpy(q, p);
break;
}
/*
* Now we search backwards along the string from `nfit',
* looking for a space at which to break the line. If we
* don't find one at all, that's fine - we'll just break
* the line at `nfit'.
*/
for (j = nfit; j > 0; j--) {
if (isspace((unsigned char)p[j])) {
nfit = j;
break;
}
}
strncpy(q, p, nfit);
q[nfit] = '\n';
q += nfit+1;
p += nfit;
while (*p && isspace((unsigned char)*p))
p++;
nlines++;
}
SelectObject(hdc, oldfont);
ReleaseDC(cp->hwnd, hdc);
if (lines) *lines = nlines;
sfree(pwidths);
return ret;
}
/*
* A single standalone static text control.
*/
void statictext(struct ctlpos *cp, char *text, int lines, int id)
{
RECT r;
r.left = GAPBETWEEN;
r.top = cp->ypos;
r.right = cp->width;
r.bottom = STATICHEIGHT * lines;
cp->ypos += r.bottom + GAPBETWEEN;
doctl(cp, r, "STATIC",
WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
0, text, id);
}
/*
* An owner-drawn static text control for a panel title.
*/
void paneltitle(struct ctlpos *cp, int id)
{
RECT r;
r.left = GAPBETWEEN;
r.top = cp->ypos;
r.right = cp->width;
r.bottom = TITLEHEIGHT;
cp->ypos += r.bottom + GAPBETWEEN;
doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_OWNERDRAW,
0, NULL, id);
}
/*
* A button on the right hand side, with a static to its left.
*/
void staticbtn(struct ctlpos *cp, char *stext, int sid,
char *btext, int bid)
{
const int height = (PUSHBTNHEIGHT > STATICHEIGHT ?
PUSHBTNHEIGHT : STATICHEIGHT);
RECT r;
int lwid, rwid, rpos;
rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
lwid = rpos - 2 * GAPBETWEEN;
rwid = cp->width + GAPBETWEEN - rpos;
r.left = GAPBETWEEN;
r.top = cp->ypos + (height - STATICHEIGHT) / 2;
r.right = lwid;
r.bottom = STATICHEIGHT;
doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
r.left = rpos;
r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
r.right = rwid;
r.bottom = PUSHBTNHEIGHT;
doctl(cp, r, "BUTTON",
BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
0, btext, bid);
cp->ypos += height + GAPBETWEEN;
}
/*
* A simple push button.
*/
void button(struct ctlpos *cp, char *btext, int bid, bool defbtn)
{
RECT r;
r.left = GAPBETWEEN;
r.top = cp->ypos;
r.right = cp->width;
r.bottom = PUSHBTNHEIGHT;
/* Q67655: the _dialog box_ must know which button is default
* as well as the button itself knowing */
if (defbtn && cp->hwnd)
SendMessage(cp->hwnd, DM_SETDEFID, bid, 0);
doctl(cp, r, "BUTTON",
BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP |
(defbtn ? BS_DEFPUSHBUTTON : 0) | BS_PUSHBUTTON,
0, btext, bid);
cp->ypos += PUSHBTNHEIGHT + GAPBETWEEN;
}
/*
* Like staticbtn, but two buttons.
*/
void static2btn(struct ctlpos *cp, char *stext, int sid,
char *btext1, int bid1, char *btext2, int bid2)
{
const int height = (PUSHBTNHEIGHT > STATICHEIGHT ?
PUSHBTNHEIGHT : STATICHEIGHT);
RECT r;
int lwid, rwid1, rwid2, rpos1, rpos2;
rpos1 = GAPBETWEEN + (cp->width + GAPBETWEEN) / 2;
rpos2 = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
lwid = rpos1 - 2 * GAPBETWEEN;
rwid1 = rpos2 - rpos1 - GAPBETWEEN;
rwid2 = cp->width + GAPBETWEEN - rpos2;
r.left = GAPBETWEEN;
r.top = cp->ypos + (height - STATICHEIGHT) / 2;
r.right = lwid;
r.bottom = STATICHEIGHT;
doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
r.left = rpos1;
r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
r.right = rwid1;
r.bottom = PUSHBTNHEIGHT;
doctl(cp, r, "BUTTON",
BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
0, btext1, bid1);
r.left = rpos2;
r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
r.right = rwid2;
r.bottom = PUSHBTNHEIGHT;
doctl(cp, r, "BUTTON",
BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
0, btext2, bid2);
cp->ypos += height + GAPBETWEEN;
}
/*
* An edit control on the right hand side, with a static to its left.
*/
static void staticedit_internal(struct ctlpos *cp, char *stext,
int sid, int eid, int percentedit,
int style)
{
const int height = (EDITHEIGHT > STATICHEIGHT ?
EDITHEIGHT : STATICHEIGHT);
RECT r;
int lwid, rwid, rpos;
rpos =
GAPBETWEEN + (100 - percentedit) * (cp->width + GAPBETWEEN) / 100;
lwid = rpos - 2 * GAPBETWEEN;
rwid = cp->width + GAPBETWEEN - rpos;
r.left = GAPBETWEEN;
r.top = cp->ypos + (height - STATICHEIGHT) / 2;
r.right = lwid;
r.bottom = STATICHEIGHT;
doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
r.left = rpos;
r.top = cp->ypos + (height - EDITHEIGHT) / 2;
r.right = rwid;
r.bottom = EDITHEIGHT;
doctl(cp, r, "EDIT",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | style,
WS_EX_CLIENTEDGE, "", eid);
cp->ypos += height + GAPBETWEEN;
}
void staticedit(struct ctlpos *cp, char *stext,
int sid, int eid, int percentedit)
{
staticedit_internal(cp, stext, sid, eid, percentedit, 0);
}
void staticpassedit(struct ctlpos *cp, char *stext,
int sid, int eid, int percentedit)
{
staticedit_internal(cp, stext, sid, eid, percentedit, ES_PASSWORD);
}
/*
* A drop-down list box on the right hand side, with a static to
* its left.
*/
void staticddl(struct ctlpos *cp, char *stext,
int sid, int lid, int percentlist)
{
const int height = (COMBOHEIGHT > STATICHEIGHT ?
COMBOHEIGHT : STATICHEIGHT);
RECT r;
int lwid, rwid, rpos;
rpos =
GAPBETWEEN + (100 - percentlist) * (cp->width + GAPBETWEEN) / 100;
lwid = rpos - 2 * GAPBETWEEN;
rwid = cp->width + GAPBETWEEN - rpos;
r.left = GAPBETWEEN;
r.top = cp->ypos + (height - STATICHEIGHT) / 2;
r.right = lwid;
r.bottom = STATICHEIGHT;
doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
r.left = rpos;
r.top = cp->ypos + (height - EDITHEIGHT) / 2;
r.right = rwid;
r.bottom = COMBOHEIGHT*4;
doctl(cp, r, "COMBOBOX",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
cp->ypos += height + GAPBETWEEN;
}
/*
* A combo box on the right hand side, with a static to its left.
*/
void staticcombo(struct ctlpos *cp, char *stext,
int sid, int lid, int percentlist)
{
const int height = (COMBOHEIGHT > STATICHEIGHT ?
COMBOHEIGHT : STATICHEIGHT);
RECT r;
int lwid, rwid, rpos;
rpos =
GAPBETWEEN + (100 - percentlist) * (cp->width + GAPBETWEEN) / 100;
lwid = rpos - 2 * GAPBETWEEN;
rwid = cp->width + GAPBETWEEN - rpos;
r.left = GAPBETWEEN;
r.top = cp->ypos + (height - STATICHEIGHT) / 2;
r.right = lwid;
r.bottom = STATICHEIGHT;
doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
r.left = rpos;
r.top = cp->ypos + (height - EDITHEIGHT) / 2;
r.right = rwid;
r.bottom = COMBOHEIGHT*10;
doctl(cp, r, "COMBOBOX",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
CBS_DROPDOWN | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
cp->ypos += height + GAPBETWEEN;
}
/*
* A static, with a full-width drop-down list box below it.
*/
void staticddlbig(struct ctlpos *cp, char *stext,
int sid, int lid)
{
RECT r;
if (stext) {
r.left = GAPBETWEEN;
r.top = cp->ypos;
r.right = cp->width;
r.bottom = STATICHEIGHT;
doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
cp->ypos += STATICHEIGHT;
}
r.left = GAPBETWEEN;
r.top = cp->ypos;
r.right = cp->width;
r.bottom = COMBOHEIGHT*4;
doctl(cp, r, "COMBOBOX",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
cp->ypos += COMBOHEIGHT + GAPBETWEEN;
}
/*
* A big multiline edit control with a static labelling it.
*/
void bigeditctrl(struct ctlpos *cp, char *stext,
int sid, int eid, int lines)
{
RECT r;
if (stext) {
r.left = GAPBETWEEN;
r.top = cp->ypos;
r.right = cp->width;
r.bottom = STATICHEIGHT;
cp->ypos += r.bottom + GAPWITHIN;
doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
}
r.left = GAPBETWEEN;
r.top = cp->ypos;
r.right = cp->width;
r.bottom = EDITHEIGHT + (lines - 1) * STATICHEIGHT;
cp->ypos += r.bottom + GAPBETWEEN;
doctl(cp, r, "EDIT",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL | ES_MULTILINE,
WS_EX_CLIENTEDGE, "", eid);
}
/*
* A list box with a static labelling it.
*/
void listbox(struct ctlpos *cp, char *stext,
int sid, int lid, int lines, bool multi)
{
RECT r;
if (stext != NULL) {
r.left = GAPBETWEEN;
r.top = cp->ypos;
r.right = cp->width;
r.bottom = STATICHEIGHT;
cp->ypos += r.bottom + GAPWITHIN;
doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
}
r.left = GAPBETWEEN;
r.top = cp->ypos;
r.right = cp->width;
r.bottom = LISTHEIGHT + (lines - 1) * LISTINCREMENT;
cp->ypos += r.bottom + GAPBETWEEN;
doctl(cp, r, "LISTBOX",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_VSCROLL |
LBS_NOTIFY | LBS_HASSTRINGS | LBS_USETABSTOPS |
(multi ? LBS_MULTIPLESEL : 0),
WS_EX_CLIENTEDGE, "", lid);
}
/*
* A tab-control substitute when a real tab control is unavailable.
*/
void ersatztab(struct ctlpos *cp, char *stext, int sid, int lid, int s2id)
{
const int height = (COMBOHEIGHT > STATICHEIGHT ?
COMBOHEIGHT : STATICHEIGHT);
RECT r;
int bigwid, lwid, rwid, rpos;
static const int BIGGAP = 15;
static const int MEDGAP = 3;
bigwid = cp->width + 2 * GAPBETWEEN - 2 * BIGGAP;
cp->ypos += MEDGAP;
rpos = BIGGAP + (bigwid + BIGGAP) / 2;
lwid = rpos - 2 * BIGGAP;
rwid = bigwid + BIGGAP - rpos;
r.left = BIGGAP;
r.top = cp->ypos + (height - STATICHEIGHT) / 2;
r.right = lwid;
r.bottom = STATICHEIGHT;
doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
r.left = rpos;
r.top = cp->ypos + (height - COMBOHEIGHT) / 2;
r.right = rwid;
r.bottom = COMBOHEIGHT * 10;
doctl(cp, r, "COMBOBOX",
WS_CHILD | WS_VISIBLE | WS_TABSTOP |
CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_CLIENTEDGE, "", lid);
cp->ypos += height + MEDGAP + GAPBETWEEN;
r.left = GAPBETWEEN;
r.top = cp->ypos;
r.right = cp->width;
r.bottom = 2;
doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
0, "", s2id);
}
/*
* A static line, followed by an edit control on the left hand side
* and a button on the right.
*/
void editbutton(struct ctlpos *cp, char *stext, int sid,
int eid, char *btext, int bid)
{
const int height = (EDITHEIGHT > PUSHBTNHEIGHT ?
EDITHEIGHT : PUSHBTNHEIGHT);
RECT r;
int lwid, rwid, rpos;
r.left = GAPBETWEEN;
r.top = cp->ypos;
r.right = cp->width;
r.bottom = STATICHEIGHT;
cp->ypos += r.bottom + GAPWITHIN;
doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
rpos = GAPBETWEEN + 3 * (cp->width + GAPBETWEEN) / 4;
lwid = rpos - 2 * GAPBETWEEN;
rwid = cp->width + GAPBETWEEN - rpos;
r.left = GAPBETWEEN;
r.top = cp->ypos + (height - EDITHEIGHT) / 2;
r.right = lwid;
r.bottom = EDITHEIGHT;
doctl(cp, r, "EDIT",
WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL,
WS_EX_CLIENTEDGE, "", eid);
r.left = rpos;
r.top = cp->ypos + (height - PUSHBTNHEIGHT) / 2;
r.right = rwid;
r.bottom = PUSHBTNHEIGHT;
doctl(cp, r, "BUTTON",
BS_NOTIFY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
0, btext, bid);
cp->ypos += height + GAPBETWEEN;
}
/*
* A special control for manipulating an ordered preference list
* (eg. for cipher selection).
* XXX: this is a rough hack and could be improved.
*/
void prefslist(struct prefslist *hdl, struct ctlpos *cp, int lines,
char *stext, int sid, int listid, int upbid, int dnbid)
{
const static int percents[] = { 5, 75, 20 };
RECT r;
int xpos, percent = 0, i;
int listheight = LISTHEIGHT + (lines - 1) * LISTINCREMENT;
const int BTNSHEIGHT = 2*PUSHBTNHEIGHT + GAPBETWEEN;
int totalheight, buttonpos;
/* Squirrel away IDs. */
hdl->listid = listid;
hdl->upbid = upbid;
hdl->dnbid = dnbid;
/* The static label. */
if (stext != NULL) {
r.left = GAPBETWEEN;
r.top = cp->ypos;
r.right = cp->width;
r.bottom = STATICHEIGHT;
cp->ypos += r.bottom + GAPWITHIN;
doctl(cp, r, "STATIC", WS_CHILD | WS_VISIBLE, 0, stext, sid);
}
if (listheight > BTNSHEIGHT) {
totalheight = listheight;
buttonpos = (listheight - BTNSHEIGHT) / 2;
} else {
totalheight = BTNSHEIGHT;
buttonpos = 0;
}
for (i=0; i<3; i++) {
int left, wid;
xpos = (cp->width + GAPBETWEEN) * percent / 100;
left = xpos + GAPBETWEEN;
percent += percents[i];
xpos = (cp->width + GAPBETWEEN) * percent / 100;
wid = xpos - left;
switch (i) {
case 1: {
/* The drag list box. */
r.left = left; r.right = wid;
r.top = cp->ypos; r.bottom = listheight;
HWND ctl = doctl(cp, r, "LISTBOX",
WS_CHILD | WS_VISIBLE | WS_TABSTOP |
WS_VSCROLL | LBS_HASSTRINGS | LBS_USETABSTOPS,
WS_EX_CLIENTEDGE,
"", listid);
p_MakeDragList(ctl);
break;
}
case 2:
/* The "Up" and "Down" buttons. */
/* XXX worry about accelerators if we have more than one
* prefslist on a panel */
r.left = left; r.right = wid;
r.top = cp->ypos + buttonpos; r.bottom = PUSHBTNHEIGHT;
doctl(cp, r, "BUTTON",
BS_NOTIFY | WS_CHILD | WS_VISIBLE |
WS_TABSTOP | BS_PUSHBUTTON,
0, "&Up", upbid);
r.left = left; r.right = wid;
r.top = cp->ypos + buttonpos + PUSHBTNHEIGHT + GAPBETWEEN;
r.bottom = PUSHBTNHEIGHT;
doctl(cp, r, "BUTTON",
BS_NOTIFY | WS_CHILD | WS_VISIBLE |
WS_TABSTOP | BS_PUSHBUTTON,
0, "&Down", dnbid);
break;
}
}
cp->ypos += totalheight + GAPBETWEEN;
}
/*
* Helper function for prefslist: move item in list box.
*/
static void pl_moveitem(HWND hwnd, int listid, int src, int dst)
{
int tlen, val;
char *txt;
/* Get the item's data. */
tlen = SendDlgItemMessage (hwnd, listid, LB_GETTEXTLEN, src, 0);
txt = snewn(tlen+1, char);
SendDlgItemMessage (hwnd, listid, LB_GETTEXT, src, (LPARAM) txt);
val = SendDlgItemMessage (hwnd, listid, LB_GETITEMDATA, src, 0);
/* Deselect old location. */
SendDlgItemMessage (hwnd, listid, LB_SETSEL, false, src);
/* Delete it at the old location. */
SendDlgItemMessage (hwnd, listid, LB_DELETESTRING, src, 0);
/* Insert it at new location. */
SendDlgItemMessage (hwnd, listid, LB_INSERTSTRING, dst,
(LPARAM) txt);
SendDlgItemMessage (hwnd, listid, LB_SETITEMDATA, dst,
(LPARAM) val);
/* Set selection. */
SendDlgItemMessage (hwnd, listid, LB_SETCURSEL, dst, 0);
sfree (txt);
}
int pl_itemfrompt(HWND hwnd, POINT cursor, bool scroll)
{
int ret;
POINT uppoint, downpoint;
int updist, downdist, upitem, downitem, i;
/*
* Ghastly hackery to try to figure out not which
* _item_, but which _gap between items_, the user
* is pointing at. We do this by first working out
* which list item is under the cursor, and then
* working out how far the cursor would have to
* move up or down before the answer was different.
* Then we put the insertion point _above_ the
* current item if the upper edge is closer than
* the lower edge, or _below_ it if vice versa.
*/
ret = p_LBItemFromPt(hwnd, cursor, scroll);
if (ret == -1)
return ret;
ret = p_LBItemFromPt(hwnd, cursor, false);
updist = downdist = 0;
for (i = 1; i < 4096 && (!updist || !downdist); i++) {
uppoint = downpoint = cursor;
uppoint.y -= i;
downpoint.y += i;
upitem = p_LBItemFromPt(hwnd, uppoint, false);
downitem = p_LBItemFromPt(hwnd, downpoint, false);
if (!updist && upitem != ret)
updist = i;
if (!downdist && downitem != ret)
downdist = i;
}
if (downdist < updist)
ret++;
return ret;
}
/*
* Handler for prefslist above.
*
* Return value has bit 0 set if the dialog box procedure needs to
* return true from handling this message; it has bit 1 set if a
* change may have been made in the contents of the list.
*/
int handle_prefslist(struct prefslist *hdl,
int *array, int maxmemb,
bool is_dlmsg, HWND hwnd,
WPARAM wParam, LPARAM lParam)
{
int i;
int ret = 0;
if (is_dlmsg) {
if ((int)wParam == hdl->listid) {
DRAGLISTINFO *dlm = (DRAGLISTINFO *)lParam;
int dest = 0; /* initialise to placate gcc */
switch (dlm->uNotification) {
case DL_BEGINDRAG:
/* Add a dummy item to make pl_itemfrompt() work
* better.
* FIXME: this causes scrollbar glitches if the count of
* listbox contains >= its height. */
hdl->dummyitem =
SendDlgItemMessage(hwnd, hdl->listid,
LB_ADDSTRING, 0, (LPARAM) "");
hdl->srcitem = p_LBItemFromPt(dlm->hWnd, dlm->ptCursor, true);
hdl->dragging = false;
/* XXX hack Q183115 */
SetWindowLongPtr(hwnd, DWLP_MSGRESULT, true);
ret |= 1; break;
case DL_CANCELDRAG:
p_DrawInsert(hwnd, dlm->hWnd, -1); /* Clear arrow */
SendDlgItemMessage(hwnd, hdl->listid,
LB_DELETESTRING, hdl->dummyitem, 0);
hdl->dragging = false;
ret |= 1; break;
case DL_DRAGGING:
hdl->dragging = true;
dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, true);
if (dest > hdl->dummyitem) dest = hdl->dummyitem;
p_DrawInsert (hwnd, dlm->hWnd, dest);
if (dest >= 0)
SetWindowLongPtr(hwnd, DWLP_MSGRESULT, DL_MOVECURSOR);
else
SetWindowLongPtr(hwnd, DWLP_MSGRESULT, DL_STOPCURSOR);
ret |= 1; break;
case DL_DROPPED:
if (hdl->dragging) {
dest = pl_itemfrompt(dlm->hWnd, dlm->ptCursor, true);
if (dest > hdl->dummyitem) dest = hdl->dummyitem;
p_DrawInsert (hwnd, dlm->hWnd, -1);
}
SendDlgItemMessage(hwnd, hdl->listid,
LB_DELETESTRING, hdl->dummyitem, 0);
if (hdl->dragging) {
hdl->dragging = false;
if (dest >= 0) {
/* Correct for "missing" item. */
if (dest > hdl->srcitem) dest--;
pl_moveitem(hwnd, hdl->listid, hdl->srcitem, dest);
}
ret |= 2;
}
ret |= 1; break;
}
}
} else {
if (((LOWORD(wParam) == hdl->upbid) ||
(LOWORD(wParam) == hdl->dnbid)) &&
((HIWORD(wParam) == BN_CLICKED) ||
(HIWORD(wParam) == BN_DOUBLECLICKED))) {
/* Move an item up or down the list. */
/* Get the current selection, if any. */
int selection = SendDlgItemMessage (hwnd, hdl->listid, LB_GETCURSEL, 0, 0);
if (selection == LB_ERR) {
MessageBeep(0);
} else {
int nitems;
/* Get the total number of items. */
nitems = SendDlgItemMessage (hwnd, hdl->listid, LB_GETCOUNT, 0, 0);
/* Should we do anything? */
if (LOWORD(wParam) == hdl->upbid && (selection > 0))
pl_moveitem(hwnd, hdl->listid, selection, selection - 1);
else if (LOWORD(wParam) == hdl->dnbid && (selection < nitems - 1))
pl_moveitem(hwnd, hdl->listid, selection, selection + 1);
ret |= 2;
}
}
}
if (array) {
/* Update array to match the list box. */
for (i=0; i < maxmemb; i++)
array[i] = SendDlgItemMessage (hwnd, hdl->listid, LB_GETITEMDATA,
i, 0);
}
return ret;
}
/*
* A progress bar (from Common Controls). We like our progress bars
* to be smooth and unbroken, without those ugly divisions; some
* older compilers may not support that, but that's life.
*/
void progressbar(struct ctlpos *cp, int id)
{
RECT r;
r.left = GAPBETWEEN;
r.top = cp->ypos;
r.right = cp->width;
r.bottom = PROGBARHEIGHT;
cp->ypos += r.bottom + GAPBETWEEN;
doctl(cp, r, PROGRESS_CLASS, WS_CHILD | WS_VISIBLE
#ifdef PBS_SMOOTH
| PBS_SMOOTH
#endif
, WS_EX_CLIENTEDGE, "", id);
}
/* ----------------------------------------------------------------------
* Platform-specific side of portable dialog-box mechanism.
*/
/*
* This function takes a string, escapes all the ampersands, and
* places a single (unescaped) ampersand in front of the first
* occurrence of the given shortcut character (which may be
* NO_SHORTCUT).
*
* Return value is a malloc'ed copy of the processed version of the
* string.
*/
static char *shortcut_escape(const char *text, char shortcut)
{
char *ret;
char const *p;
char *q;
if (!text)
return NULL; /* sfree won't choke on this */
ret = snewn(2*strlen(text)+1, char); /* size potentially doubles! */
shortcut = tolower((unsigned char)shortcut);
p = text;
q = ret;
while (*p) {
if (shortcut != NO_SHORTCUT &&
tolower((unsigned char)*p) == shortcut) {
*q++ = '&';
shortcut = NO_SHORTCUT; /* stop it happening twice */
} else if (*p == '&') {
*q++ = '&';
}
*q++ = *p++;
}
*q = '\0';
return ret;
}
void winctrl_add_shortcuts(struct dlgparam *dp, struct winctrl *c)
{
int i;
for (i = 0; i < lenof(c->shortcuts); i++)
if (c->shortcuts[i] != NO_SHORTCUT) {
unsigned char s = tolower((unsigned char)c->shortcuts[i]);
assert(!dp->shortcuts[s]);
dp->shortcuts[s] = true;
}
}
void winctrl_rem_shortcuts(struct dlgparam *dp, struct winctrl *c)
{
int i;
for (i = 0; i < lenof(c->shortcuts); i++)
if (c->shortcuts[i] != NO_SHORTCUT) {
unsigned char s = tolower((unsigned char)c->shortcuts[i]);
assert(dp->shortcuts[s]);
dp->shortcuts[s] = false;
}
}
static int winctrl_cmp_byctrl(void *av, void *bv)
{
struct winctrl *a = (struct winctrl *)av;
struct winctrl *b = (struct winctrl *)bv;
if (a->ctrl < b->ctrl)
return -1;
else if (a->ctrl > b->ctrl)
return +1;
else
return 0;
}
static int winctrl_cmp_byid(void *av, void *bv)
{
struct winctrl *a = (struct winctrl *)av;
struct winctrl *b = (struct winctrl *)bv;
if (a->base_id < b->base_id)
return -1;
else if (a->base_id > b->base_id)
return +1;
else
return 0;
}
static int winctrl_cmp_byctrl_find(void *av, void *bv)
{
union control *a = (union control *)av;
struct winctrl *b = (struct winctrl *)bv;
if (a < b->ctrl)
return -1;
else if (a > b->ctrl)
return +1;
else
return 0;
}
static int winctrl_cmp_byid_find(void *av, void *bv)
{
int *a = (int *)av;
struct winctrl *b = (struct winctrl *)bv;
if (*a < b->base_id)
return -1;
else if (*a >= b->base_id + b->num_ids)
return +1;
else
return 0;
}
void winctrl_init(struct winctrls *wc)
{
wc->byctrl = newtree234(winctrl_cmp_byctrl);
wc->byid = newtree234(winctrl_cmp_byid);
}
void winctrl_cleanup(struct winctrls *wc)
{
struct winctrl *c;
while ((c = index234(wc->byid, 0)) != NULL) {
winctrl_remove(wc, c);
sfree(c->data);
sfree(c);
}
freetree234(wc->byctrl);
freetree234(wc->byid);
wc->byctrl = wc->byid = NULL;
}
void winctrl_add(struct winctrls *wc, struct winctrl *c)
{
struct winctrl *ret;
if (c->ctrl) {
ret = add234(wc->byctrl, c);
assert(ret == c);
}
ret = add234(wc->byid, c);
assert(ret == c);
}
void winctrl_remove(struct winctrls *wc, struct winctrl *c)
{
struct winctrl *ret;
ret = del234(wc->byctrl, c);
ret = del234(wc->byid, c);
assert(ret == c);
}
struct winctrl *winctrl_findbyctrl(struct winctrls *wc, union control *ctrl)
{
return find234(wc->byctrl, ctrl, winctrl_cmp_byctrl_find);
}
struct winctrl *winctrl_findbyid(struct winctrls *wc, int id)
{
return find234(wc->byid, &id, winctrl_cmp_byid_find);
}
struct winctrl *winctrl_findbyindex(struct winctrls *wc, int index)
{
return index234(wc->byid, index);
}
static void move_windows(HWND hwnd, int base_id, int num_ids, LONG dy)
{
if (!dy)
return;
for (int i = 0; i < num_ids; i++) {
HWND win = GetDlgItem(hwnd, base_id + i);
RECT rect;
if (!GetWindowRect(win, &rect))
continue;
POINT p;
p.x = rect.left;
p.y = rect.top + dy;
if (!ScreenToClient(hwnd, &p))
continue;
SetWindowPos(win, NULL, p.x, p.y, 0, 0,
SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
}
}
void winctrl_layout(struct dlgparam *dp, struct winctrls *wc,
struct ctlpos *cp, struct controlset *s, int *id)
{
struct ctlpos columns[16];
int ncols, colstart, colspan;
struct ctlpos tabdelays[16];
union control *tabdelayed[16];
int ntabdelays;
struct ctlpos pos;
char shortcuts[MAX_SHORTCUTS_PER_CTRL];
int nshortcuts;
char *escaped;
int i, actual_base_id, base_id, num_ids, align_id_relative;
void *data;
base_id = *id;
/* Start a containing box, if we have a boxname. */
if (s->boxname && *s->boxname) {
struct winctrl *c = snew(struct winctrl);
c->ctrl = NULL;
c->base_id = c->align_id = base_id;
c->num_ids = 1;
c->data = NULL;
memset(c->shortcuts, NO_SHORTCUT, lenof(c->shortcuts));
winctrl_add(wc, c);
beginbox(cp, s->boxtitle, base_id);
base_id++;
}
/* Draw a title, if we have one. */
if (!s->boxname && s->boxtitle) {
struct winctrl *c = snew(struct winctrl);
c->ctrl = NULL;
c->base_id = c->align_id = base_id;
c->num_ids = 1;
c->data = dupstr(s->boxtitle);
memset(c->shortcuts, NO_SHORTCUT, lenof(c->shortcuts));
winctrl_add(wc, c);
paneltitle(cp, base_id);
base_id++;
}
/* Initially we have just one column. */
ncols = 1;
columns[0] = *cp; /* structure copy */
/* And initially, there are no pending tab-delayed controls. */
ntabdelays = 0;
/* Loop over each control in the controlset. */
for (i = 0; i < s->ncontrols; i++) {
union control *ctrl = s->ctrls[i];
/*
* Generic processing that pertains to all control types.
* At the end of this if statement, we'll have produced
* `ctrl' (a pointer to the control we have to create, or
* think about creating, in this iteration of the loop),
* `pos' (a suitable ctlpos with which to position it), and
* `c' (a winctrl structure to receive details of the
* dialog IDs). Or we'll have done a `continue', if it was
* CTRL_COLUMNS and doesn't require any control creation at
* all.
*/
if (ctrl->generic.type == CTRL_COLUMNS) {
assert((ctrl->columns.ncols == 1) ^ (ncols == 1));
if (ncols == 1) {
/*
* We're splitting into multiple columns.
*/
int lpercent, rpercent, lx, rx, i;
ncols = ctrl->columns.ncols;
assert(ncols <= lenof(columns));
for (i = 1; i < ncols; i++)
columns[i] = columns[0]; /* structure copy */
lpercent = 0;
for (i = 0; i < ncols; i++) {
rpercent = lpercent + ctrl->columns.percentages[i];
lx = columns[i].xoff + lpercent *
(columns[i].width + GAPBETWEEN) / 100;
rx = columns[i].xoff + rpercent *
(columns[i].width + GAPBETWEEN) / 100;
columns[i].xoff = lx;
columns[i].width = rx - lx - GAPBETWEEN;
lpercent = rpercent;
}
} else {
/*
* We're recombining the various columns into one.
*/
int maxy = columns[0].ypos;
int i;
for (i = 1; i < ncols; i++)
if (maxy < columns[i].ypos)
maxy = columns[i].ypos;
ncols = 1;
columns[0] = *cp; /* structure copy */
columns[0].ypos = maxy;
}
continue;
} else if (ctrl->generic.type == CTRL_TABDELAY) {
int i;
assert(!ctrl->generic.tabdelay);
ctrl = ctrl->tabdelay.ctrl;
for (i = 0; i < ntabdelays; i++)
if (tabdelayed[i] == ctrl)
break;
assert(i < ntabdelays); /* we have to have found it */
pos = tabdelays[i]; /* structure copy */
colstart = colspan = -1; /* indicate this was tab-delayed */
} else {
/*
* If it wasn't one of those, it's a genuine control;
* so we'll have to compute a position for it now, by
* checking its column span.
*/
int col;
colstart = COLUMN_START(ctrl->generic.column);
colspan = COLUMN_SPAN(ctrl->generic.column);
pos = columns[colstart]; /* structure copy */
pos.width = columns[colstart+colspan-1].width +
(columns[colstart+colspan-1].xoff - columns[colstart].xoff);
for (col = colstart; col < colstart+colspan; col++)
if (pos.ypos < columns[col].ypos)
pos.ypos = columns[col].ypos;
/*
* If this control is to be tabdelayed, add it to the
* tabdelay list, and unset pos.hwnd to inhibit actual
* control creation.
*/
if (ctrl->generic.tabdelay) {
assert(ntabdelays < lenof(tabdelays));
tabdelays[ntabdelays] = pos; /* structure copy */
tabdelayed[ntabdelays] = ctrl;
ntabdelays++;
pos.hwnd = NULL;
}
}
/* Most controls don't need anything in c->data. */
data = NULL;
/* And they all start off with no shortcuts registered. */
memset(shortcuts, NO_SHORTCUT, lenof(shortcuts));
nshortcuts = 0;
/* Almost all controls start at base_id. */
actual_base_id = base_id;
/* For vertical alignment purposes, the most relevant control
* in a group is usually the last one. But that can be
* overridden occasionally. */
align_id_relative = -1;
/*
* Now we're ready to actually create the control, by
* switching on its type.
*/
switch (ctrl->generic.type) {
case CTRL_TEXT: {
char *wrapped, *escaped;
int lines;
num_ids = 1;
wrapped = staticwrap(&pos, cp->hwnd,
ctrl->generic.label, &lines);
escaped = shortcut_escape(wrapped, NO_SHORTCUT);
statictext(&pos, escaped, lines, base_id);
sfree(escaped);
sfree(wrapped);
break;
}
case CTRL_EDITBOX:
num_ids = 2; /* static, edit */
escaped = shortcut_escape(ctrl->editbox.label,
ctrl->editbox.shortcut);
shortcuts[nshortcuts++] = ctrl->editbox.shortcut;
if (ctrl->editbox.percentwidth == 100) {
if (ctrl->editbox.has_list)
combobox(&pos, escaped,
base_id, base_id+1);
else
editboxfw(&pos, ctrl->editbox.password, escaped,
base_id, base_id+1);
} else {
if (ctrl->editbox.has_list) {
staticcombo(&pos, escaped, base_id, base_id+1,
ctrl->editbox.percentwidth);
} else {
(ctrl->editbox.password ? staticpassedit : staticedit)
(&pos, escaped, base_id, base_id+1,
ctrl->editbox.percentwidth);
}
}
sfree(escaped);
break;
case CTRL_RADIO: {
num_ids = ctrl->radio.nbuttons + 1; /* label as well */
struct radio *buttons;
int i;
escaped = shortcut_escape(ctrl->radio.label,
ctrl->radio.shortcut);
shortcuts[nshortcuts++] = ctrl->radio.shortcut;
buttons = snewn(ctrl->radio.nbuttons, struct radio);
for (i = 0; i < ctrl->radio.nbuttons; i++) {
buttons[i].text =
shortcut_escape(ctrl->radio.buttons[i],
(char)(ctrl->radio.shortcuts ?
ctrl->radio.shortcuts[i] :
NO_SHORTCUT));
buttons[i].id = base_id + 1 + i;
if (ctrl->radio.shortcuts) {
assert(nshortcuts < MAX_SHORTCUTS_PER_CTRL);
shortcuts[nshortcuts++] = ctrl->radio.shortcuts[i];
}
}
radioline_common(&pos, escaped, base_id,
ctrl->radio.ncolumns,
buttons, ctrl->radio.nbuttons);
for (i = 0; i < ctrl->radio.nbuttons; i++) {
sfree(buttons[i].text);
}
sfree(buttons);
sfree(escaped);
break;
}
case CTRL_CHECKBOX:
num_ids = 1;
escaped = shortcut_escape(ctrl->checkbox.label,
ctrl->checkbox.shortcut);
shortcuts[nshortcuts++] = ctrl->checkbox.shortcut;
checkbox(&pos, escaped, base_id);
sfree(escaped);
break;
case CTRL_BUTTON:
escaped = shortcut_escape(ctrl->button.label,
ctrl->button.shortcut);
shortcuts[nshortcuts++] = ctrl->button.shortcut;
if (ctrl->button.iscancel)
actual_base_id = IDCANCEL;
num_ids = 1;
button(&pos, escaped, actual_base_id, ctrl->button.isdefault);
sfree(escaped);
break;
case CTRL_LISTBOX:
num_ids = 2;
escaped = shortcut_escape(ctrl->listbox.label,
ctrl->listbox.shortcut);
shortcuts[nshortcuts++] = ctrl->listbox.shortcut;
if (ctrl->listbox.draglist) {
data = snew(struct prefslist);
num_ids = 4;
prefslist(data, &pos, ctrl->listbox.height, escaped,
base_id, base_id+1, base_id+2, base_id+3);
shortcuts[nshortcuts++] = 'u'; /* Up */
shortcuts[nshortcuts++] = 'd'; /* Down */
} else if (ctrl->listbox.height == 0) {
/* Drop-down list. */
if (ctrl->listbox.percentwidth == 100) {
staticddlbig(&pos, escaped,
base_id, base_id+1);
} else {
staticddl(&pos, escaped, base_id,
base_id+1, ctrl->listbox.percentwidth);
}
} else {
/* Ordinary list. */
listbox(&pos, escaped, base_id, base_id+1,
ctrl->listbox.height, ctrl->listbox.multisel);
}
if (ctrl->listbox.ncols) {
/*
* This method of getting the box width is a bit of
* a hack; we'd do better to try to retrieve the
* actual width in dialog units from doctl() just
* before MapDialogRect. But that's going to be no
* fun, and this should be good enough accuracy.
*/
int width = cp->width * ctrl->listbox.percentwidth;
int *tabarray;
int i, percent;
tabarray = snewn(ctrl->listbox.ncols-1, int);
percent = 0;
for (i = 0; i < ctrl->listbox.ncols-1; i++) {
percent += ctrl->listbox.percentages[i];
tabarray[i] = width * percent / 10000;
}
SendDlgItemMessage(cp->hwnd, base_id+1, LB_SETTABSTOPS,
ctrl->listbox.ncols-1, (LPARAM)tabarray);
sfree(tabarray);
}
sfree(escaped);
break;
case CTRL_FILESELECT:
num_ids = 3;
escaped = shortcut_escape(ctrl->fileselect.label,
ctrl->fileselect.shortcut);
shortcuts[nshortcuts++] = ctrl->fileselect.shortcut;
editbutton(&pos, escaped, base_id, base_id+1,
"Bro&wse...", base_id+2);
shortcuts[nshortcuts++] = 'w';
sfree(escaped);
break;
case CTRL_FONTSELECT:
num_ids = 3;
escaped = shortcut_escape(ctrl->fontselect.label,
ctrl->fontselect.shortcut);
shortcuts[nshortcuts++] = ctrl->fontselect.shortcut;
statictext(&pos, escaped, 1, base_id);
staticbtn(&pos, "", base_id+1, "Change...", base_id+2);
data = fontspec_new("", false, 0, 0);
sfree(escaped);
break;
default:
unreachable("bad control type in winctrl_layout");
}
/* Translate the original align_id_relative of -1 into n-1 */
if (align_id_relative < 0)
align_id_relative += num_ids;
/*
* Create a `struct winctrl' for this control, and advance
* the dialog ID counter, if it's actually been created
* (and isn't tabdelayed).
*/
if (pos.hwnd) {
struct winctrl *c = snew(struct winctrl);
c->ctrl = ctrl;
c->base_id = actual_base_id;
c->align_id = c->base_id + align_id_relative;
c->num_ids = num_ids;
c->data = data;
memcpy(c->shortcuts, shortcuts, sizeof(shortcuts));
winctrl_add(wc, c);
winctrl_add_shortcuts(dp, c);
if (actual_base_id == base_id)
base_id += num_ids;
if (ctrl->generic.align_next_to) {
/*
* Implement align_next_to by looking at the y extents
* of the two controls now that both are created, and
* moving one or the other downwards so that they're
* centred on a common horizontal line.
*/
struct winctrl *c2 = winctrl_findbyctrl(
wc, ctrl->generic.align_next_to);
HWND win1 = GetDlgItem(pos.hwnd, c->align_id);
HWND win2 = GetDlgItem(pos.hwnd, c2->align_id);
RECT rect1, rect2;
if (win1 && win2 &&
GetWindowRect(win1, &rect1) &&
GetWindowRect(win2, &rect2)) {
LONG top = (rect1.top < rect2.top ? rect1.top : rect2.top);
LONG bottom = (rect1.bottom > rect2.bottom ?
rect1.bottom : rect2.bottom);
move_windows(pos.hwnd, c->base_id, c->num_ids,
(top + bottom - rect1.top - rect1.bottom)/2);
move_windows(pos.hwnd, c2->base_id, c2->num_ids,
(top + bottom - rect2.top - rect2.bottom)/2);
}
}
} else {
sfree(data);
}
if (colstart >= 0) {
/*
* Update the ypos in all columns crossed by this
* control.
*/
int i;
for (i = colstart; i < colstart+colspan; i++)
columns[i].ypos = pos.ypos;
}
}
/*
* We've now finished laying out the controls; so now update
* the ctlpos and control ID that were passed in, terminate
* any containing box, and return.
*/
for (i = 0; i < ncols; i++)
if (cp->ypos < columns[i].ypos)
cp->ypos = columns[i].ypos;
*id = base_id;
if (s->boxname && *s->boxname)
endbox(cp);
}
static void winctrl_set_focus(union control *ctrl, struct dlgparam *dp,
bool has_focus)
{
if (has_focus) {
if (dp->focused)
dp->lastfocused = dp->focused;
dp->focused = ctrl;
} else if (!has_focus && dp->focused == ctrl) {
dp->lastfocused = dp->focused;
dp->focused = NULL;
}
}
union control *dlg_last_focused(union control *ctrl, dlgparam *dp)
{
return dp->focused == ctrl ? dp->lastfocused : dp->focused;
}
/*
* The dialog-box procedure calls this function to handle Windows
* messages on a control we manage.
*/
bool winctrl_handle_command(struct dlgparam *dp, UINT msg,
WPARAM wParam, LPARAM lParam)
{
struct winctrl *c;
union control *ctrl;
int i, id;
bool ret;
static UINT draglistmsg = WM_NULL;
/*
* Filter out pointless window messages. Our interest is in
* WM_COMMAND and the drag list message, and nothing else.
*/
if (draglistmsg == WM_NULL)
draglistmsg = RegisterWindowMessage (DRAGLISTMSGSTRING);
if (msg != draglistmsg && msg != WM_COMMAND && msg != WM_DRAWITEM)
return false;
/*
* Look up the control ID in our data.
*/
c = NULL;
for (i = 0; i < dp->nctrltrees; i++) {
c = winctrl_findbyid(dp->controltrees[i], LOWORD(wParam));
if (c)
break;
}
if (!c)
return false; /* we have nothing to do */
if (msg == WM_DRAWITEM) {
/*
* Owner-draw request for a panel title.
*/
LPDRAWITEMSTRUCT di = (LPDRAWITEMSTRUCT) lParam;
HDC hdc = di->hDC;
RECT r = di->rcItem;
SIZE s;
SetMapMode(hdc, MM_TEXT); /* ensure logical units == pixels */
GetTextExtentPoint32(hdc, (char *)c->data,
strlen((char *)c->data), &s);
DrawEdge(hdc, &r, EDGE_ETCHED, BF_ADJUST | BF_RECT);
TextOut(hdc,
r.left + (r.right-r.left-s.cx)/2,
r.top + (r.bottom-r.top-s.cy)/2,
(char *)c->data, strlen((char *)c->data));
return true;
}
ctrl = c->ctrl;
id = LOWORD(wParam) - c->base_id;
if (!ctrl || !ctrl->generic.handler)
return false; /* nothing we can do here */
/*
* From here on we do not issue `return' statements until the
* very end of the dialog box: any event handler is entitled to
* ask for a colour selector, so we _must_ always allow control
* to reach the end of this switch statement so that the
* subsequent code can test dp->coloursel_wanted().
*/
ret = false;
dp->coloursel_wanted = false;
/*
* Now switch on the control type and the message.
*/
switch (ctrl->generic.type) {
case CTRL_EDITBOX:
if (msg == WM_COMMAND && !ctrl->editbox.has_list &&
(HIWORD(wParam) == EN_SETFOCUS || HIWORD(wParam) == EN_KILLFOCUS))
winctrl_set_focus(ctrl, dp, HIWORD(wParam) == EN_SETFOCUS);
if (msg == WM_COMMAND && ctrl->editbox.has_list &&
(HIWORD(wParam)==CBN_SETFOCUS || HIWORD(wParam)==CBN_KILLFOCUS))
winctrl_set_focus(ctrl, dp, HIWORD(wParam) == CBN_SETFOCUS);
if (msg == WM_COMMAND && !ctrl->editbox.has_list &&
HIWORD(wParam) == EN_CHANGE)
ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
if (msg == WM_COMMAND &&
ctrl->editbox.has_list) {
if (HIWORD(wParam) == CBN_SELCHANGE) {
int index, len;
char *text;
index = SendDlgItemMessage(dp->hwnd, c->base_id+1,
CB_GETCURSEL, 0, 0);
len = SendDlgItemMessage(dp->hwnd, c->base_id+1,
CB_GETLBTEXTLEN, index, 0);
text = snewn(len+1, char);
SendDlgItemMessage(dp->hwnd, c->base_id+1, CB_GETLBTEXT,
index, (LPARAM)text);
SetDlgItemText(dp->hwnd, c->base_id+1, text);
sfree(text);
ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
} else if (HIWORD(wParam) == CBN_EDITCHANGE) {
ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
} else if (HIWORD(wParam) == CBN_KILLFOCUS) {
ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH);
}
}
break;
case CTRL_RADIO:
if (msg == WM_COMMAND &&
(HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
/*
* We sometimes get spurious BN_CLICKED messages for the
* radio button that is just about to _lose_ selection, if
* we're switching using the arrow keys. Therefore we
* double-check that the button in wParam is actually
* checked before generating an event.
*/
if (msg == WM_COMMAND &&
(HIWORD(wParam) == BN_CLICKED ||
HIWORD(wParam) == BN_DOUBLECLICKED) &&
IsDlgButtonChecked(dp->hwnd, LOWORD(wParam))) {
ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
}
break;
case CTRL_CHECKBOX:
if (msg == WM_COMMAND &&
(HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
if (msg == WM_COMMAND &&
(HIWORD(wParam) == BN_CLICKED ||
HIWORD(wParam) == BN_DOUBLECLICKED)) {
ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
}
break;
case CTRL_BUTTON:
if (msg == WM_COMMAND &&
(HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
if (msg == WM_COMMAND &&
(HIWORD(wParam) == BN_CLICKED ||
HIWORD(wParam) == BN_DOUBLECLICKED)) {
ctrl->generic.handler(ctrl, dp, dp->data, EVENT_ACTION);
}
break;
case CTRL_LISTBOX:
if (msg == WM_COMMAND && ctrl->listbox.height != 0 &&
(HIWORD(wParam)==LBN_SETFOCUS || HIWORD(wParam)==LBN_KILLFOCUS))
winctrl_set_focus(ctrl, dp, HIWORD(wParam) == LBN_SETFOCUS);
if (msg == WM_COMMAND && ctrl->listbox.height == 0 &&
(HIWORD(wParam)==CBN_SETFOCUS || HIWORD(wParam)==CBN_KILLFOCUS))
winctrl_set_focus(ctrl, dp, HIWORD(wParam) == CBN_SETFOCUS);
if (msg == WM_COMMAND && id >= 2 &&
(HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
if (ctrl->listbox.draglist) {
int pret;
pret = handle_prefslist(c->data, NULL, 0, (msg != WM_COMMAND),
dp->hwnd, wParam, lParam);
if (pret & 2)
ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
ret = pret & 1;
} else {
if (msg == WM_COMMAND && HIWORD(wParam) == LBN_DBLCLK) {
SetCapture(dp->hwnd);
ctrl->generic.handler(ctrl, dp, dp->data, EVENT_ACTION);
} else if (msg == WM_COMMAND && HIWORD(wParam) == LBN_SELCHANGE) {
ctrl->generic.handler(ctrl, dp, dp->data, EVENT_SELCHANGE);
}
}
break;
case CTRL_FILESELECT:
if (msg == WM_COMMAND && id == 1 &&
(HIWORD(wParam) == EN_SETFOCUS || HIWORD(wParam) == EN_KILLFOCUS))
winctrl_set_focus(ctrl, dp, HIWORD(wParam) == EN_SETFOCUS);
if (msg == WM_COMMAND && id == 2 &&
(HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
if (msg == WM_COMMAND && id == 1 && HIWORD(wParam) == EN_CHANGE)
ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
if (id == 2 &&
(msg == WM_COMMAND &&
(HIWORD(wParam) == BN_CLICKED ||
HIWORD(wParam) == BN_DOUBLECLICKED))) {
OPENFILENAME of;
char filename[FILENAME_MAX];
memset(&of, 0, sizeof(of));
of.hwndOwner = dp->hwnd;
if (ctrl->fileselect.filter)
of.lpstrFilter = ctrl->fileselect.filter;
else
of.lpstrFilter = "All Files (*.*)\0*\0\0\0";
of.lpstrCustomFilter = NULL;
of.nFilterIndex = 1;
of.lpstrFile = filename;
GetDlgItemText(dp->hwnd, c->base_id+1, filename, lenof(filename));
filename[lenof(filename)-1] = '\0';
of.nMaxFile = lenof(filename);
of.lpstrFileTitle = NULL;
of.lpstrTitle = ctrl->fileselect.title;
of.Flags = 0;
if (request_file(NULL, &of, false, ctrl->fileselect.for_writing)) {
SetDlgItemText(dp->hwnd, c->base_id + 1, filename);
ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
}
}
break;
case CTRL_FONTSELECT:
if (msg == WM_COMMAND && id == 2 &&
(HIWORD(wParam) == BN_SETFOCUS || HIWORD(wParam) == BN_KILLFOCUS))
winctrl_set_focus(ctrl, dp, HIWORD(wParam) == BN_SETFOCUS);
if (id == 2 &&
(msg == WM_COMMAND &&
(HIWORD(wParam) == BN_CLICKED ||
HIWORD(wParam) == BN_DOUBLECLICKED))) {
CHOOSEFONT cf;
LOGFONT lf;
HDC hdc;
FontSpec *fs = (FontSpec *)c->data;
hdc = GetDC(0);
lf.lfHeight = -MulDiv(fs->height,
GetDeviceCaps(hdc, LOGPIXELSY), 72);
ReleaseDC(0, hdc);
lf.lfWidth = lf.lfEscapement = lf.lfOrientation = 0;
lf.lfItalic = lf.lfUnderline = lf.lfStrikeOut = 0;
lf.lfWeight = (fs->isbold ? FW_BOLD : 0);
lf.lfCharSet = fs->charset;
lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
lf.lfQuality = DEFAULT_QUALITY;
lf.lfPitchAndFamily = FIXED_PITCH | FF_DONTCARE;
strncpy(lf.lfFaceName, fs->name,
sizeof(lf.lfFaceName) - 1);
lf.lfFaceName[sizeof(lf.lfFaceName) - 1] = '\0';
cf.lStructSize = sizeof(cf);
cf.hwndOwner = dp->hwnd;
cf.lpLogFont = &lf;
cf.Flags = (dp->fixed_pitch_fonts ? CF_FIXEDPITCHONLY : 0) |
CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
if (ChooseFont(&cf)) {
fs = fontspec_new(lf.lfFaceName, (lf.lfWeight == FW_BOLD),
cf.iPointSize / 10, lf.lfCharSet);
dlg_fontsel_set(ctrl, dp, fs);
fontspec_free(fs);
ctrl->generic.handler(ctrl, dp, dp->data, EVENT_VALCHANGE);
}
}
break;
}
/*
* If the above event handler has asked for a colour selector,
* now is the time to generate one.
*/
if (dp->coloursel_wanted) {
static CHOOSECOLOR cc;
static DWORD custom[16] = { 0 }; /* zero initialisers */
cc.lStructSize = sizeof(cc);
cc.hwndOwner = dp->hwnd;
cc.hInstance = (HWND) hinst;
cc.lpCustColors = custom;
cc.rgbResult = RGB(dp->coloursel_result.r,
dp->coloursel_result.g,
dp->coloursel_result.b);
cc.Flags = CC_FULLOPEN | CC_RGBINIT;
if (ChooseColor(&cc)) {
dp->coloursel_result.r =
(unsigned char) (cc.rgbResult & 0xFF);
dp->coloursel_result.g =
(unsigned char) (cc.rgbResult >> 8) & 0xFF;
dp->coloursel_result.b =
(unsigned char) (cc.rgbResult >> 16) & 0xFF;
dp->coloursel_result.ok = true;
} else
dp->coloursel_result.ok = false;
ctrl->generic.handler(ctrl, dp, dp->data, EVENT_CALLBACK);
}
return ret;
}
/*
* This function can be called to produce context help on a
* control. Returns true if it has actually launched some help.
*/
bool winctrl_context_help(struct dlgparam *dp, HWND hwnd, int id)
{
int i;
struct winctrl *c;
/*
* Look up the control ID in our data.
*/
c = NULL;
for (i = 0; i < dp->nctrltrees; i++) {
c = winctrl_findbyid(dp->controltrees[i], id);
if (c)
break;
}
if (!c)
return false; /* we have nothing to do */
/*
* This is the Windows front end, so we're allowed to assume
* `helpctx.p' is a context string.
*/
if (!c->ctrl || !c->ctrl->generic.helpctx.p)
return false; /* no help available for this ctrl */
launch_help(hwnd, c->ctrl->generic.helpctx.p);
return true;
}
/*
* Now the various functions that the platform-independent
* mechanism can call to access the dialog box entries.
*/
static struct winctrl *dlg_findbyctrl(struct dlgparam *dp, union control *ctrl)
{
int i;
for (i = 0; i < dp->nctrltrees; i++) {
struct winctrl *c = winctrl_findbyctrl(dp->controltrees[i], ctrl);
if (c)
return c;
}
return NULL;
}
bool dlg_is_visible(union control *ctrl, dlgparam *dp)
{
/*
* In this implementation of the dialog box, we physically
* uncreate controls that aren't in a visible panel of the config
* box. So we can tell if a control is visible just by checking if
* it _exists_.
*/
return dlg_findbyctrl(dp, ctrl) != NULL;
}
void dlg_radiobutton_set(union control *ctrl, dlgparam *dp, int whichbutton)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
assert(c && c->ctrl->generic.type == CTRL_RADIO);
CheckRadioButton(dp->hwnd,
c->base_id + 1,
c->base_id + c->ctrl->radio.nbuttons,
c->base_id + 1 + whichbutton);
}
int dlg_radiobutton_get(union control *ctrl, dlgparam *dp)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
int i;
assert(c && c->ctrl->generic.type == CTRL_RADIO);
for (i = 0; i < c->ctrl->radio.nbuttons; i++)
if (IsDlgButtonChecked(dp->hwnd, c->base_id + 1 + i))
return i;
unreachable("no radio button was checked");
}
void dlg_checkbox_set(union control *ctrl, dlgparam *dp, bool checked)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
assert(c && c->ctrl->generic.type == CTRL_CHECKBOX);
CheckDlgButton(dp->hwnd, c->base_id, checked);
}
bool dlg_checkbox_get(union control *ctrl, dlgparam *dp)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
assert(c && c->ctrl->generic.type == CTRL_CHECKBOX);
return 0 != IsDlgButtonChecked(dp->hwnd, c->base_id);
}
void dlg_editbox_set(union control *ctrl, dlgparam *dp, char const *text)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
SetDlgItemText(dp->hwnd, c->base_id+1, text);
}
char *dlg_editbox_get(union control *ctrl, dlgparam *dp)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
return GetDlgItemText_alloc(dp->hwnd, c->base_id+1);
}
/* The `listbox' functions can also apply to combo boxes. */
void dlg_listbox_clear(union control *ctrl, dlgparam *dp)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
int msg;
assert(c &&
(c->ctrl->generic.type == CTRL_LISTBOX ||
(c->ctrl->generic.type == CTRL_EDITBOX &&
c->ctrl->editbox.has_list)));
msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
LB_RESETCONTENT : CB_RESETCONTENT);
SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, 0);
}
void dlg_listbox_del(union control *ctrl, dlgparam *dp, int index)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
int msg;
assert(c &&
(c->ctrl->generic.type == CTRL_LISTBOX ||
(c->ctrl->generic.type == CTRL_EDITBOX &&
c->ctrl->editbox.has_list)));
msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
LB_DELETESTRING : CB_DELETESTRING);
SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0);
}
void dlg_listbox_add(union control *ctrl, dlgparam *dp, char const *text)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
int msg;
assert(c &&
(c->ctrl->generic.type == CTRL_LISTBOX ||
(c->ctrl->generic.type == CTRL_EDITBOX &&
c->ctrl->editbox.has_list)));
msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
LB_ADDSTRING : CB_ADDSTRING);
SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, (LPARAM)text);
}
/*
* Each listbox entry may have a numeric id associated with it.
* Note that some front ends only permit a string to be stored at
* each position, which means that _if_ you put two identical
* strings in any listbox then you MUST not assign them different
* IDs and expect to get meaningful results back.
*/
void dlg_listbox_addwithid(union control *ctrl, dlgparam *dp,
char const *text, int id)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
int msg, msg2, index;
assert(c &&
(c->ctrl->generic.type == CTRL_LISTBOX ||
(c->ctrl->generic.type == CTRL_EDITBOX &&
c->ctrl->editbox.has_list)));
msg = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
LB_ADDSTRING : CB_ADDSTRING);
msg2 = (c->ctrl->generic.type==CTRL_LISTBOX && c->ctrl->listbox.height!=0 ?
LB_SETITEMDATA : CB_SETITEMDATA);
index = SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, (LPARAM)text);
SendDlgItemMessage(dp->hwnd, c->base_id+1, msg2, index, (LPARAM)id);
}
int dlg_listbox_getid(union control *ctrl, dlgparam *dp, int index)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
int msg;
assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
msg = (c->ctrl->listbox.height != 0 ? LB_GETITEMDATA : CB_GETITEMDATA);
return
SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0);
}
/* dlg_listbox_index returns <0 if no single element is selected. */
int dlg_listbox_index(union control *ctrl, dlgparam *dp)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
int msg, ret;
assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
if (c->ctrl->listbox.multisel) {
assert(c->ctrl->listbox.height != 0); /* not combo box */
ret = SendDlgItemMessage(dp->hwnd, c->base_id+1, LB_GETSELCOUNT, 0, 0);
if (ret == LB_ERR || ret > 1)
return -1;
}
msg = (c->ctrl->listbox.height != 0 ? LB_GETCURSEL : CB_GETCURSEL);
ret = SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, 0, 0);
if (ret == LB_ERR)
return -1;
else
return ret;
}
bool dlg_listbox_issel(union control *ctrl, dlgparam *dp, int index)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
c->ctrl->listbox.multisel &&
c->ctrl->listbox.height != 0);
return
SendDlgItemMessage(dp->hwnd, c->base_id+1, LB_GETSEL, index, 0);
}
void dlg_listbox_select(union control *ctrl, dlgparam *dp, int index)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
int msg;
assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
!c->ctrl->listbox.multisel);
msg = (c->ctrl->listbox.height != 0 ? LB_SETCURSEL : CB_SETCURSEL);
SendDlgItemMessage(dp->hwnd, c->base_id+1, msg, index, 0);
}
void dlg_text_set(union control *ctrl, dlgparam *dp, char const *text)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
assert(c && c->ctrl->generic.type == CTRL_TEXT);
SetDlgItemText(dp->hwnd, c->base_id, text);
}
void dlg_label_change(union control *ctrl, dlgparam *dp, char const *text)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
char *escaped = NULL;
int id = -1;
assert(c);
switch (c->ctrl->generic.type) {
case CTRL_EDITBOX:
escaped = shortcut_escape(text, c->ctrl->editbox.shortcut);
id = c->base_id;
break;
case CTRL_RADIO:
escaped = shortcut_escape(text, c->ctrl->radio.shortcut);
id = c->base_id;
break;
case CTRL_CHECKBOX:
escaped = shortcut_escape(text, ctrl->checkbox.shortcut);
id = c->base_id;
break;
case CTRL_BUTTON:
escaped = shortcut_escape(text, ctrl->button.shortcut);
id = c->base_id;
break;
case CTRL_LISTBOX:
escaped = shortcut_escape(text, ctrl->listbox.shortcut);
id = c->base_id;
break;
case CTRL_FILESELECT:
escaped = shortcut_escape(text, ctrl->fileselect.shortcut);
id = c->base_id;
break;
case CTRL_FONTSELECT:
escaped = shortcut_escape(text, ctrl->fontselect.shortcut);
id = c->base_id;
break;
default:
unreachable("bad control type in label_change");
}
if (escaped) {
SetDlgItemText(dp->hwnd, id, escaped);
sfree(escaped);
}
}
void dlg_filesel_set(union control *ctrl, dlgparam *dp, Filename *fn)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
assert(c && c->ctrl->generic.type == CTRL_FILESELECT);
SetDlgItemText(dp->hwnd, c->base_id+1, fn->path);
}
Filename *dlg_filesel_get(union control *ctrl, dlgparam *dp)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
char *tmp;
Filename *ret;
assert(c && c->ctrl->generic.type == CTRL_FILESELECT);
tmp = GetDlgItemText_alloc(dp->hwnd, c->base_id+1);
ret = filename_from_str(tmp);
sfree(tmp);
return ret;
}
void dlg_fontsel_set(union control *ctrl, dlgparam *dp, FontSpec *fs)
{
char *buf, *boldstr;
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
assert(c && c->ctrl->generic.type == CTRL_FONTSELECT);
fontspec_free((FontSpec *)c->data);
c->data = fontspec_copy(fs);
boldstr = (fs->isbold ? "bold, " : "");
if (fs->height == 0)
buf = dupprintf("Font: %s, %sdefault height", fs->name, boldstr);
else
buf = dupprintf("Font: %s, %s%d-%s", fs->name, boldstr,
(fs->height < 0 ? -fs->height : fs->height),
(fs->height < 0 ? "pixel" : "point"));
SetDlgItemText(dp->hwnd, c->base_id+1, buf);
sfree(buf);
dlg_auto_set_fixed_pitch_flag(dp);
}
FontSpec *dlg_fontsel_get(union control *ctrl, dlgparam *dp)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
assert(c && c->ctrl->generic.type == CTRL_FONTSELECT);
return fontspec_copy((FontSpec *)c->data);
}
/*
* Bracketing a large set of updates in these two functions will
* cause the front end (if possible) to delay updating the screen
* until it's all complete, thus avoiding flicker.
*/
void dlg_update_start(union control *ctrl, dlgparam *dp)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
if (c && c->ctrl->generic.type == CTRL_LISTBOX) {
SendDlgItemMessage(dp->hwnd, c->base_id+1, WM_SETREDRAW, false, 0);
}
}
void dlg_update_done(union control *ctrl, dlgparam *dp)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
if (c && c->ctrl->generic.type == CTRL_LISTBOX) {
HWND hw = GetDlgItem(dp->hwnd, c->base_id+1);
SendMessage(hw, WM_SETREDRAW, true, 0);
InvalidateRect(hw, NULL, true);
}
}
void dlg_set_focus(union control *ctrl, dlgparam *dp)
{
struct winctrl *c = dlg_findbyctrl(dp, ctrl);
int id;
HWND ctl;
if (!c)
return;
switch (ctrl->generic.type) {
case CTRL_EDITBOX: id = c->base_id + 1; break;
case CTRL_RADIO:
for (id = c->base_id + ctrl->radio.nbuttons; id > 1; id--)
if (IsDlgButtonChecked(dp->hwnd, id))
break;
/*
* In the theoretically-unlikely case that no button was
* selected, id should come out of this as 1, which is a
* reasonable enough choice.
*/
break;
case CTRL_CHECKBOX: id = c->base_id; break;
case CTRL_BUTTON: id = c->base_id; break;
case CTRL_LISTBOX: id = c->base_id + 1; break;
case CTRL_FILESELECT: id = c->base_id + 1; break;
case CTRL_FONTSELECT: id = c->base_id + 2; break;
default: id = c->base_id; break;
}
ctl = GetDlgItem(dp->hwnd, id);
SetFocus(ctl);
}
/*
* During event processing, you might well want to give an error
* indication to the user. dlg_beep() is a quick and easy generic
* error; dlg_error() puts up a message-box or equivalent.
*/
void dlg_beep(dlgparam *dp)
{
MessageBeep(0);
}
void dlg_error_msg(dlgparam *dp, const char *msg)
{
MessageBox(dp->hwnd, msg,
dp->errtitle ? dp->errtitle : NULL,
MB_OK | MB_ICONERROR);
}
/*
* This function signals to the front end that the dialog's
* processing is completed, and passes an integer value (typically
* a success status).
*/
void dlg_end(dlgparam *dp, int value)
{
dp->ended = true;
dp->endresult = value;
}
void dlg_refresh(union control *ctrl, dlgparam *dp)
{
int i, j;
struct winctrl *c;
if (!ctrl) {
/*
* Send EVENT_REFRESH to absolutely everything.
*/
for (j = 0; j < dp->nctrltrees; j++) {
for (i = 0;
(c = winctrl_findbyindex(dp->controltrees[j], i)) != NULL;
i++) {
if (c->ctrl && c->ctrl->generic.handler != NULL)
c->ctrl->generic.handler(c->ctrl, dp,
dp->data, EVENT_REFRESH);
}
}
} else {
/*
* Send EVENT_REFRESH to a specific control.
*/
if (ctrl->generic.handler != NULL)
ctrl->generic.handler(ctrl, dp, dp->data, EVENT_REFRESH);
}
}
void dlg_coloursel_start(union control *ctrl, dlgparam *dp, int r, int g, int b)
{
dp->coloursel_wanted = true;
dp->coloursel_result.r = r;
dp->coloursel_result.g = g;
dp->coloursel_result.b = b;
}
bool dlg_coloursel_results(union control *ctrl, dlgparam *dp,
int *r, int *g, int *b)
{
if (dp->coloursel_result.ok) {
*r = dp->coloursel_result.r;
*g = dp->coloursel_result.g;
*b = dp->coloursel_result.b;
return true;
} else
return false;
}
void dlg_auto_set_fixed_pitch_flag(dlgparam *dp)
{
Conf *conf = (Conf *)dp->data;
FontSpec *fs;
int quality;
HFONT hfont;
HDC hdc;
TEXTMETRIC tm;
bool is_var;
/*
* Attempt to load the current font, and see if it's
* variable-pitch. If so, start off the fixed-pitch flag for the
* dialog box as false.
*
* We assume here that any client of the dlg_* mechanism which is
* using font selectors at all is also using a normal 'Conf *'
* as dp->data.
*/
quality = conf_get_int(conf, CONF_font_quality);
fs = conf_get_fontspec(conf, CONF_font);
hfont = CreateFont(0, 0, 0, 0, FW_DONTCARE, false, false, false,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality),
FIXED_PITCH | FF_DONTCARE, fs->name);
hdc = GetDC(NULL);
if (hdc && SelectObject(hdc, hfont) && GetTextMetrics(hdc, &tm)) {
/* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */
is_var = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH);
} else {
is_var = false; /* assume it's basically normal */
}
if (hdc)
ReleaseDC(NULL, hdc);
if (hfont)
DeleteObject(hfont);
if (is_var)
dp->fixed_pitch_fonts = false;
}
bool dlg_get_fixed_pitch_flag(dlgparam *dp)
{
return dp->fixed_pitch_fonts;
}
void dlg_set_fixed_pitch_flag(dlgparam *dp, bool flag)
{
dp->fixed_pitch_fonts = flag;
}
void dp_init(struct dlgparam *dp)
{
dp->nctrltrees = 0;
dp->data = NULL;
dp->ended = false;
dp->focused = dp->lastfocused = NULL;
memset(dp->shortcuts, 0, sizeof(dp->shortcuts));
dp->hwnd = NULL;
dp->wintitle = dp->errtitle = NULL;
dp->fixed_pitch_fonts = true;
}
void dp_add_tree(struct dlgparam *dp, struct winctrls *wc)
{
assert(dp->nctrltrees < lenof(dp->controltrees));
dp->controltrees[dp->nctrltrees++] = wc;
}
void dp_cleanup(struct dlgparam *dp)
{
sfree(dp->wintitle);
sfree(dp->errtitle);
}