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;
}


Reply via email to