I tried to make a dialog in WinBoard that shows all the options an engine
has defined through feature option="..." commands, and allows you to set them.
(For UCI engine running under Michel's Polyglot you would see all UCI options
there.) It seems to work very nice for me (Win XP, Win 2K), but for some
others (Using Win98SE or Win ME) it is a disaster:
http://www.talkchess.com/forum/viewtopic.php?t=29286
I am afraid this is a bit beyond my knowledge of Windows API, and I don't have
a Win 9 on which I can experiment. Does anyone have any idea what could be
the difference between XP/2k and 98/ME that could cause such a dramatic
problem?
/*
* Engine-settings dialog. The complexity come from an attempt to present the
engine-defined options
* in a nicey formatted layout. To this end we first run a back-end
pre-formatter, which will distribute
* the controls over two columns (the minimum required, as some are double
width). It also takes care of
* grouping options that start with the same word (mainly for "Polyglot ..."
options). It assigns relative
* suitability to break points between lines, and in the end decides if and
where to break up the list
* for display in multiple (2*N) columns.
* The thus obtained list representing the topology of the layout is then
passed to a front-end routine
* that generates the actual dialog box from it.
*/
//#include "config.h"
#include "config.h"
#include <stdio.h>
#include "common.h"
#include "backend.h"
int layoutList[2*MAX_OPTIONS];
int checkList[2*MAX_OPTIONS];
int comboList[2*MAX_OPTIONS];
int buttonList[2*MAX_OPTIONS];
int breaks[MAX_OPTIONS];
int checks, combos, buttons, layout;
void
PrintOpt(int i, int right, ChessProgramState *cps)
{
if(i<0) {
if(!right) fprintf(debugFP, "%30s", "");
} else {
Option opt = cps->option[i];
switch(opt.type) {
case Spin:
fprintf(debugFP, "%20.20s [ +/-]", opt.name);
break;
case TextBox:
fprintf(debugFP, "%20.20s
[______________________________________]", opt.name);
break;
case CheckBox:
fprintf(debugFP, "[x] %-26.25s", opt.name);
break;
case ComboBox:
fprintf(debugFP, "%20.20s [ COMBO ]", opt.name);
break;
case Button:
case SaveButton:
fprintf(debugFP, "[ %26.26s ]", opt.name);
break;
}
}
fprintf(debugFP, right ? "\n" : " ");
}
void
CreateOptionDialogTest(int *list, int nr, ChessProgramState *cps)
{
int line;
for(line = 0; line < nr; line+=2) {
PrintOpt(list[line+1], 0, cps);
PrintOpt(list[line], 1, cps);
}
}
void
LayoutOptions(int firstOption, int endOption, char *groupName, Option
*optionList)
{
int n, i, b = strlen(groupName), stop, prefix, right, nextOption,
firstButton = buttons;
Control lastType, nextType;
nextOption = firstOption;
while(nextOption < endOption) {
checks = combos = 0; stop = 0;
lastType = Button; // kludge to make sure leading Spin will not be
prefixed
// first separate out buttons for later treatment, and collect
consecutive checks and combos
while(nextOption < endOption && !stop) {
switch(nextType = optionList[nextOption].type) {
case CheckBox: checkList[checks++] = nextOption; lastType =
CheckBox; break;
case ComboBox: comboList[combos++] = nextOption; lastType =
ComboBox; break;
case SaveButton:
case Button: buttonList[checks++] = nextOption; lastType =
Button; break;
case TextBox:
case Spin: stop++;
}
nextOption++;
}
// We now must be at the end, or looking at a spin or textbox (in
nextType)
if(!stop)
nextType = Button; // kudge to flush remaining checks and combos
undistorted
// Take a new line if a spin follows combos or checks, or when we
encounter a textbox
if((combos+checks || nextType == TextBox) && layout&1) {
layoutList[layout++] = -1;
}
// The last check or combo before a spin will be put on the same line
as that spin (prefix)
// and will thus not be grouped with other checks and combos
prefix = -1;
if(nextType == Spin && lastType != Button) {
if(lastType == CheckBox) prefix = checkList[--checks]; else
if(lastType == ComboBox) prefix = comboList[--combos];
}
// if a combo is followed by a textbox, it must stay at the end of the
combo/checks list to appear
// immediately above the textbox, so treat it as check. (A check would
automatically be and remain there.)
if(nextType == TextBox && lastType == ComboBox)
checkList[checks++] = comboList[--combos];
// Now append the checks behind the (remaining) combos to treat them as
one group
for(i=0; i< checks; i++)
comboList[combos++] = checkList[i];
// emit the consecutive checks and combos in two columns
right = combos/2; // rounded down if odd!
for(i=0; i<right; i++) {
breaks[layout/2] = 2;
layoutList[layout++] = comboList[i];
layoutList[layout++] = comboList[i + right];
}
// An odd check or combo (which could belong to following textBox) will
be put in the left column
// If there was an even number of checks and combos the last one will
automatically be in that position
if(combos&1) {
layoutList[layout++] = -1;
layoutList[layout++] = comboList[2*right];
}
if(nextType == TextBox) {
// A textBox is double width, so must be left-adjusted, and the
right column remains empty
breaks[layout/2] = lastType == Button ? 0 : 100;
layoutList[layout++] = -1;
layoutList[layout++] = nextOption - 1;
} else if(nextType == Spin) {
// A spin will go in the next available position (right to left!).
If it had to be prefixed with
// a check or combo, this next position must be to the right, and
the prefix goes left to it.
layoutList[layout++] = nextOption - 1;
if(prefix >= 0) layoutList[layout++] = prefix;
}
}
// take a new line if needed
if(layout&1) layoutList[layout++] = -1;
// emit the buttons belonging in this group; loose buttons are saved for
last, to appear at bottom of dialog
if(b) {
while(buttons > firstButton)
layoutList[layout++] = buttonList[--buttons];
if(layout&1) layoutList[layout++] = -1;
}
}
void
DesignOptionDialog(ChessProgramState *cps)
{
int k=0, n=0;
char buf[MSG_SIZ];
layout = 0;
buttons = 0;
while(k < cps->nrOptions) { // k steps through 'solitary' options
// look if we hit a group of options having names that start with the
same word
int groupSize = 1;
sscanf(cps->option[k].name, "%s", &buf); // get first word of option
name
while(k + groupSize < cps->nrOptions &&
strstr(cps->option[k+groupSize].name, buf) ==
cps->option[k+groupSize].name) groupSize++;
if(groupSize > 3) {
// We found a group to terminates the current section
LayoutOptions(n, k, "", cps->option); // flush all solitary
options appearing before the group
LayoutOptions(k, k+groupSize, buf, cps->option); // flush the
group
k = n = k + groupSize;
} else k += groupSize; // small groups are grouped with the solytary
options
}
if(n != k) LayoutOptions(n, k, "", cps->option); // flush remaining
solitary options
// decide if and where we break into two column pairs
// Emit buttons and add OK and cancel
for(k=0; k<buttons; k++) layoutList[layout++] = buttonList[k];
// Create the dialog window
if(appData.debugMode) CreateOptionDialogTest(layoutList, layout, cps);
// CreateOptionDialog(layoutList, layout, cps);
}
#include <windows.h>
extern HINSTANCE hInst;
typedef struct {
DLGITEMTEMPLATE item;
WORD code;
WORD controlType;
wchar_t d1, data;
WORD creationData;
} Item;
struct {
DLGTEMPLATE header;
WORD menu;
WORD winClass;
wchar_t title[20];
WORD pointSize;
wchar_t fontName[14];
Item control[MAX_OPTIONS];
} template = {
{ DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_SETFONT, 0, 0, 0,
0, 285, 300 },
0x0000, 0x0000, L"Engine #1 Settings ", 8, L"MS Sans Serif"
};
int controlHandle[MAX_OPTIONS], textHandle[MAX_OPTIONS];
ChessProgramState *activeCps;
void
SetOptionValues(HWND hDlg, ChessProgramState *cps)
// Put all current option values in controls, and write option names next to
them
{
HANDLE hwndCombo;
int i, k;
char **choices, title[MSG_SIZ];
for(i=0; i<=layout+buttons; i++) {
int j=layoutList[i];
if(j<0) continue;
SetDlgItemText( hDlg, 2000+2*i, cps->option[j].name );
switch(cps->option[j].type) {
case Spin:
SetDlgItemInt( hDlg, 2001+2*i, cps->option[j].value, TRUE );
break;
case TextBox:
SetDlgItemText( hDlg, 2001+2*i, cps->option[j].textValue );
break;
case CheckBox:
CheckDlgButton( hDlg, 2000+2*i, cps->option[j].value != 0);
break;
case ComboBox:
choices = (char**) cps->option[j].textValue;
hwndCombo = GetDlgItem(hDlg, 2001+2*i);
SendMessage(hwndCombo, CB_RESETCONTENT, 0, 0);
for(k=0; k<cps->option[j].max; k++) {
SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM)
choices[k]);
}
SendMessage(hwndCombo, CB_SELECTSTRING, (WPARAM) -1, (LPARAM)
choices[cps->option[j].value]);
break;
case Button:
case SaveButton:
break;
}
}
SetDlgItemText( hDlg, IDOK, "OK" );
SetDlgItemText( hDlg, IDCANCEL, "Cancel" );
sprintf(title, "%s Engine Settings (%s)", cps->which, cps->tidy);
title[0] &= ~32; // capitalize
SetWindowText( hDlg, title);
}
void
GetOptionValues(HWND hDlg, ChessProgramState *cps)
// read out all controls, and if value is altered, remember it and send it to
the engine
{
HANDLE hwndCombo;
int i, k, new, changed;
char **choices, newText[MSG_SIZ], buf[MSG_SIZ];
BOOL success;
for(i=0; i<=layout; i++) {
int j=layoutList[i];
if(j<0) continue;
SetDlgItemText( hDlg, 2000+2*i, cps->option[j].name );
switch(cps->option[j].type) {
case Spin:
new = GetDlgItemInt( hDlg, 2001+2*i, &success, TRUE );
if(!success) break;
if(new < cps->option[j].min) new = cps->option[j].min;
if(new > cps->option[j].max) new = cps->option[j].max;
changed = 2*(cps->option[j].value != new);
cps->option[j].value = new;
break;
case TextBox:
case Browse:
success = GetDlgItemText( hDlg, 2001+2*i, newText, MSG_SIZ -
strlen(cps->option[j].name) - 9 );
if(!success) break;
changed = strcmp(cps->option[j].textValue, newText);
strcpy(cps->option[j].textValue, newText);
break;
case CheckBox:
new = IsDlgButtonChecked( hDlg, 2000+2*i );
changed = 2*(cps->option[j].value != new);
cps->option[j].value = new;
break;
case ComboBox:
choices = (char**) cps->option[j].textValue;
hwndCombo = GetDlgItem(hDlg, 2001+2*i);
success = GetDlgItemText( hDlg, 2001+2*i, newText, MSG_SIZ );
if(!success) break;
new = -1;
for(k=0; k<cps->option[j].max; k++) {
if(!strcmp(choices[k], newText)) new = k;
}
changed = new >= 0 && (cps->option[j].value != new);
if(changed) cps->option[j].value = new;
break;
case Button:
break; // are treated instantly, so they have been sent already
}
if(changed == 2) sprintf(buf, "option %s=%d\n", cps->option[j].name,
new); else
if(changed == 1) sprintf(buf, "option %s=%s\n", cps->option[j].name,
newText);
if(changed) SendToProgram(buf, cps);
}
}
LRESULT CALLBACK SettingsProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM
lParam)
{
static int * lpIndexFRC;
BOOL index_is_ok;
char buf[MSG_SIZ];
int i, j;
switch( message )
{
case WM_INITDIALOG:
// lpIndexFRC = (int *) lParam;
// CenterWindow(hDlg, GetWindow(hDlg, GW_OWNER));
SetOptionValues(hDlg, activeCps);
// SendDlgItemMessage( hDlg, IDC_NFG_Edit, EM_SETLIMITTEXT,
sizeof(buf)-1, 0 );
// SetDlgItemInt( hDlg, IDC_NFG_Edit, *lpIndexFRC, TRUE );
// SendDlgItemMessage( hDlg, IDC_NFG_Edit, EM_SETSEL, 0, -1 );
// SetFocus(GetDlgItem(hDlg, IDC_NFG_Edit));
break;
case WM_COMMAND:
switch( LOWORD(wParam) ) {
case IDOK:
GetOptionValues(hDlg, activeCps);
EndDialog( hDlg, 0 );
return TRUE;
case IDCANCEL:
EndDialog( hDlg, 1 );
return TRUE;
default:
// program-defined push buttons
i = LOWORD(wParam);
if( i>=2000 && i < 2000+2*(layout+buttons)) {
j = layoutList[(i - 2000)/2];
if(j < 0) break;
if( activeCps->option[j].type == SaveButton)
GetOptionValues(hDlg, activeCps);
else if( activeCps->option[j].type != Button) break;
sprintf(buf, "option %s\n", activeCps->option[j].name);
SendToProgram(buf, activeCps);
}
break;
}
break;
}
return FALSE;
}
void AddControl(int x, int y, int w, int h, int type, int style, int n)
{
int i;
i = template.header.cdit++;
template.control[i].item.style = style;
template.control[i].item.dwExtendedStyle = 0;
template.control[i].item.x = x;
template.control[i].item.y = y;
template.control[i].item.cx = w;
template.control[i].item.cy = h;
template.control[i].item.id = 2000 + n;
template.control[i].code = 0xFFFF;
template.control[i].controlType = type;
template.control[i].d1 = ' ';
template.control[i].data = 0;
template.control[i].creationData = 0;
}
void AddOption(int x, int y, Control type, int i)
{
switch(type) {
case Spin:
AddControl(x, y+1, 85, 9, 0x0082, SS_ENDELLIPSIS | WS_VISIBLE, i);
AddControl(x+85, y, 50, 11, 0x0081, ES_AUTOHSCROLL | ES_NUMBER |
WS_BORDER | WS_VISIBLE, i+1);
break;
case TextBox:
AddControl(x, y+1, 85, 9, 0x0082, SS_ENDELLIPSIS | WS_VISIBLE, i);
AddControl(x+85, y, 190, 11, 0x0081, ES_AUTOHSCROLL | WS_BORDER |
WS_VISIBLE, i+1);
break;
case CheckBox:
AddControl(x, y, 135, 11, 0x0080, BS_AUTOCHECKBOX | WS_VISIBLE, i);
break;
case ComboBox:
AddControl(x, y+1, 85, 9, 0x0082, SS_ENDELLIPSIS | WS_VISIBLE, i);
AddControl(x+85, y, 50, 11, 0x0085, CBS_AUTOHSCROLL | CBS_DROPDOWN
| WS_VISIBLE, i+1);
break;
case Button:
AddControl(x, y, 40, 15, 0x0080, BS_PUSHBUTTON | WS_VISIBLE, i);
break;
}
}
void
CreateDialogTemplate(int *layoutList, int nr, ChessProgramState *cps)
{
int i, j, x=0, y=0, buttonRows;
template.header.cdit = 0;
for(i=0; i<nr; i++) {
j = layoutList[i];
if(j<0) continue;
AddOption(x+145-140*(i&1), y+13*(i>>1)+5, cps->option[j].type, 2*i);
}
// add butons at the bottom of dialog window
y += 13*(nr>>1)+5;
buttonRows = (buttons + 2 + 5)/6; // 6 per row, ronded up
AddControl(x+240, y+18*(buttonRows-1), 40, 15, 0x0080, BS_PUSHBUTTON |
WS_VISIBLE, IDOK-2000);
AddControl(x+195, y+18*(buttonRows-1), 40, 15, 0x0080, BS_PUSHBUTTON |
WS_VISIBLE, IDCANCEL-2000);
for(i=0; i<buttons; i++) {
AddControl(x+45*(i%6)+5, y+18*(i/6), 40, 15, 0x0080, BS_PUSHBUTTON |
WS_VISIBLE, 2*(nr+i));
layoutList[nr+i] = buttonList[i];
}
template.title[8] = cps == &first ? '1' : '2';
template.header.cy = y+18*buttonRows+2;
}
void
EngineOptionsPopup(HWND hwnd, ChessProgramState *cps)
{
FARPROC lpProc = MakeProcInstance( (FARPROC) SettingsProc, hInst );
activeCps = cps;
DesignOptionDialog(cps);
CreateDialogTemplate(layoutList, layout, cps);
DialogBoxIndirect( hInst, &template.header, hwnd, (DLGPROC)lpProc );
FreeProcInstance(lpProc);
return;
}