Page 1 of 1

Overhauling the WinBoard menu code

PostPosted: 25 Aug 2008, 09:15
by H.G.Muller
I finally succeeded in installing an Ubuntu Linux on my laptop, so I can now run xboard. From a quick tour through the menus, it seems it only uses the following elements:

1) Pull-down menus, wich can contain a separator and items, where the latter can be greyed out.

2) The "Options" pull-down menu contains items that can be checked

3) A popup for entering a filename, with OK / cancel buttons

4) A popup for editing some text (PGN tags, comments)

5) Popups with items and separators for edit-position mode / piece drops

6) A popup with buttons for selecting the promotion piece

That is about all. There don't seem to be any complex layouts (with controls in columns, or grouped by box outlines), as there are in the WinBoard menus. I wonder if the latter is really essential. Currently, the WinBoard "Options -> Board...", "Options -> Engine..." and "File -> New Variant..." dialog boxes are the most structured, but I guess not much would be lost when all control elements would be shown in a single column, with separator lines between them. Perhaps the formatting of the popups could be made smart enough to layout a very long list of radiobuttons, representing mutually-exclusive choices, in multicolumn format. (As is done now by hand in the board and variant popups.)

The most fancyfeature of the WinBoard menus is that in the board popup you actually get a preview displayed of how the squares / pieces will look if you would change their colors. (And this does not fully work, as it ignores the allWhite and flipBlack options.) Can we live without that? Or should we, on the contrary, try to make more general use of such a "boardsquare" control element in the menus. E.g. by presenting the pieces as pictograms in the promotion and edit popups (5). Actually, I like the latter idea, but I am not sure it is feasible.

Suppose that the front-ends would implement a menu constructor routine, which gets passed an array of control-element descriptors. The array would be of unspecified length, a sentinel descriptor at the end indicating the list is finished. Each descriptor could be a struct, containing fields:

1) describing the type of control element:
a) clickable menu item
b) clickable menu item with a checkmark in front of it
c) tickbox (might be the same as (b)?)
d) radiobutton
e) filename type-in box + browse button
f) numeric type-in box
g) color + color-selection button
h) a font name + font-selector button
i) clickable board square/piece
j) a separator
k) a combo box
z) sentinel, perhaps indicating presence of some standard buttons like OK, cancel.

2) the text string that should be printed next to the control element.
These texts would make up the menu items, and be printed to the right of tickboxes and radiobuttons. For type-in fields they would be printed to the left of the field. A tab in the string could indicate that the part after the tab would be printed after the input field.

3) The current state of the input element, (ticked / unticked, contents of the type-in box) as a union type. E.g. TRUE / FALSE for ticked / unticked, "dN" for a Knight on a dark square, a text string of words separated by newlines for the contents of a combo box, the value of a variable with which a radiobutton corresponds (this would be compared to the actual value to decide if the radiobutton is displayed ticked / unticked).

4) a pointer (void*) indicating the action to be taken when the control element is used. For menu items this would be a pointer to a C function to be called when the item is clicked. For checkboxes it would be a pointer to a boolean variable, which will be set/cleared according to the state of the box. For type-in boxes and radiobuttons it is a pointer to the variable that should be set to the typed-in value.

As an example: the "File -> New Variant..." popup could be described in the backend as:

Code: Select all
union {
int i;
char *s;
Boolean b;
VariantClass variant;
} any;

struct {
int controlType;
char *text;
any *val;
void *result;
} item;

item newVariantMenu[] = {
 {RADIO, "normal", VariantNormal, &appData.variant},
 {RADIO, "gothic", VariantGothic, &appData.variant},
 {RADIO, "crazyhouse", VariantCrazyhouse, &appData.variant},
 {RADIO, "shogi", VariantShogi, &appData.variant},
 {RADIO, "xiangqi", VariantXiangqi, &appData.variant},
 {RADIO, "FRC", VariantFischeRandom, &appData.variant},
 ...,
 {NUMBER, "Board: width\tfiles", -1, &appData.boardWidth},
 {NUMBER, "       height\tranks", -1, &appData.boardWidth},
 {NUMBER, "Holding  with room for:\tpieces", -1, &appData.holdingsSize},
 {TEXT, "('-1' means defaults for selected variant)",0,NULL},
 {END, "OK,cancell",0,NULL}
};

item engineOptionsMenu[] = {
 {CHECK, "Ponder Next Move", appData.ponder, &appData.ponder},
 {CHECK, "Enable and Show Thinking (recommended)", TRUE, &appData.show Tinking},
 {CHECK, "Hide Thinking when Playing against Human", appData.hideThinkingFromHuman, &appData.hideThinkingFromHuman},
 {CHECK, "Periodic Updates (for Analysis Mode)", appData.periodicUpdates, &appData.periodicUpdates},
 {SEP, "Engine-Engine Matches", 0, NULL},
 {NUMBER, "Adjudicate draw after\tmoves", appData.adjudicateDrawMoves, &appData.adjudicateDrawMoves},
 {NUMBER, "Win/loss adjudication threshold\tcentiPawns", appData.adjudicateLossThreshold, &appData.adjudicateLossThreshold},
 {CHECK, "Verify Engine Claims", appData.testClaims, &appData.testClaims},
 {CHECK, "Detect Mates", appData.checkMates, &appData.checkMates},
 {CHECK, "Draw if Insufficient Material", appData.materialDraws, &appData.materialDraws},
 {CHECK, "Adjudicate Trivial Draws", appData.trivialDraws, &appData.trivialDraws},
 {SEP, "Apply:", 0, NULL},
 {NUMBER, "\tmove-rule", appData.ruleMoves, &appData.ruleMoves},
 {NUMBER, "\t-fold repeats", appData.drawRepeats, &appData.drawRepeats},
 {END, "OK,cancell", 0, NULL}
};

item fileMenu[] = {
 {ITEM, "New Game", FALSE, &Reset},
 {ITEM, "New Shuffle Game...", FALSE, &ShufflePopup},
 {ITEM, "New Variant...", FALSE, &VariantPopup},
 {SEP, NULL, 0, NULL},
 ...
 {END, "", 0, NULL}
};

void VariantPopup()
{
    PopupDriver(newVariantMenu);
    Reset(); // implies new game
}

void FileMenuEvent()
{
    PopupDriver(fileMenu);
}