Port engine grouping to Match Options dialog
[xboard.git] / dialogs.c
1 /*
2  * dialogs.c -- platform-independent code for dialogs of XBoard
3  *
4  * Copyright 2000, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
5  * ------------------------------------------------------------------------
6  *
7  * GNU XBoard is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or (at
10  * your option) any later version.
11  *
12  * GNU XBoard is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program. If not, see http://www.gnu.org/licenses/.  *
19  *
20  *------------------------------------------------------------------------
21  ** See the file ChangeLog for a revision history.  */
22
23 // [HGM] this file is the counterpart of woptions.c, containing xboard popup menus
24 // similar to those of WinBoard, to set the most common options interactively.
25
26 #include "config.h"
27
28 #include <stdio.h>
29 #include <ctype.h>
30 #include <errno.h>
31 #include <sys/types.h>
32
33 #if STDC_HEADERS
34 # include <stdlib.h>
35 # include <string.h>
36 #else /* not STDC_HEADERS */
37 extern char *getenv();
38 # if HAVE_STRING_H
39 #  include <string.h>
40 # else /* not HAVE_STRING_H */
41 #  include <strings.h>
42 # endif /* not HAVE_STRING_H */
43 #endif /* not STDC_HEADERS */
44
45 #if HAVE_UNISTD_H
46 # include <unistd.h>
47 #endif
48 #include <stdint.h>
49
50 #include "common.h"
51 #include "backend.h"
52 #include "xboard.h"
53 #include "menus.h"
54 #include "dialogs.h"
55 #include "gettext.h"
56
57 #ifdef ENABLE_NLS
58 # define  _(s) gettext (s)
59 # define N_(s) gettext_noop (s)
60 #else
61 # define  _(s) (s)
62 # define N_(s)  s
63 #endif
64
65
66 int values[MAX_OPTIONS];
67 ChessProgramState *currentCps;
68
69 //----------------------------Generic dialog --------------------------------------------
70
71 // cloned from Engine Settings dialog (and later merged with it)
72
73 char *marked[NrOfDialogs];
74 Boolean shellUp[NrOfDialogs];
75
76 void
77 MarkMenu (char *item, int dlgNr)
78 {
79     MarkMenuItem(marked[dlgNr] = item, True);
80 }
81
82 void
83 AddLine (Option *opt, char *s)
84 {
85     AppendText(opt, s);
86     AppendText(opt, "\n");
87 }
88
89 //---------------------------------------------- Update dialog controls ------------------------------------
90
91 int
92 SetCurrentComboSelection (Option *opt)
93 {
94     int j;
95     if(!opt->textValue) opt->value = *(int*)opt->target; /* numeric */else {
96         for(j=0; opt->choice[j]; j++) // look up actual value in list of possible values, to get selection nr
97             if(*(char**)opt->target && !strcmp(*(char**)opt->target, ((char**)opt->textValue)[j])) break;
98         opt->value = j + (opt->choice[j] == NULL);
99     }
100     return opt->value;
101 }
102
103 void
104 GenericUpdate (Option *opts, int selected)
105 {
106     int i, j;
107     char buf[MSG_SIZ];
108     float x;
109         for(i=0; ; i++) {
110             if(selected >= 0) { if(i < selected) continue; else if(i > selected) break; }
111             switch(opts[i].type) {
112                 case TextBox:
113                 case FileName:
114                 case PathName:
115                     SetWidgetText(&opts[i],  *(char**) opts[i].target, -1);
116                     break;
117                 case Spin:
118                     sprintf(buf, "%d", *(int*) opts[i].target);
119                     SetWidgetText(&opts[i], buf, -1);
120                     break;
121                 case Fractional:
122                     sprintf(buf, "%4.2f", *(float*) opts[i].target);
123                     SetWidgetText(&opts[i], buf, -1);
124                     break;
125                 case CheckBox:
126                     SetWidgetState(&opts[i],  *(Boolean*) opts[i].target);
127                     break;
128                 case ComboBox:
129                   if(opts[i].min & COMBO_CALLBACK) break;
130                   SetCurrentComboSelection(opts+i);
131                     // TODO: actually display this (but it is never used that way...)
132                     break;
133                 case EndMark:
134                     return;
135             default:
136                 printf("GenericUpdate: unexpected case in switch.\n");
137                 case ListBox:
138                 case Button:
139                 case SaveButton:
140                 case Label:
141                 case Break:
142               break;
143             }
144         }
145 }
146
147 //------------------------------------------- Read out dialog controls ------------------------------------
148
149 int
150 GenericReadout (Option *opts, int selected)
151 {
152     int i, j, res=1;
153     char *val;
154     char buf[MSG_SIZ], **dest;
155     float x;
156         for(i=0; ; i++) { // send all options that had to be OK-ed to engine
157             if(selected >= 0) { if(i < selected) continue; else if(i > selected) break; }
158             switch(opts[i].type) {
159                 case TextBox:
160                 case FileName:
161                 case PathName:
162                     GetWidgetText(&opts[i], &val);
163                     dest = currentCps ? &(opts[i].textValue) : (char**) opts[i].target;
164                     if(*dest == NULL || strcmp(*dest, val)) {
165                         if(currentCps) {
166                             snprintf(buf, MSG_SIZ,  "option %s=%s\n", opts[i].name, val);
167                             SendToProgram(buf, currentCps);
168                         } else {
169                             if(*dest) free(*dest);
170                             *dest = malloc(strlen(val)+1);
171                         }
172                         safeStrCpy(*dest, val, MSG_SIZ - (*dest - opts[i].name)); // copy text there
173                     }
174                     break;
175                 case Spin:
176                 case Fractional:
177                     GetWidgetText(&opts[i], &val);
178                     x = 0.0; // Initialise because sscanf() will fail if non-numeric text is entered
179                     sscanf(val, "%f", &x);
180                     if(x > opts[i].max) x = opts[i].max;
181                     if(x < opts[i].min) x = opts[i].min;
182                     if(opts[i].type == Fractional)
183                         *(float*) opts[i].target = x; // engines never have float options!
184                     else if(opts[i].value != x) {
185                         opts[i].value = x;
186                         if(currentCps) {
187                             snprintf(buf, MSG_SIZ,  "option %s=%.0f\n", opts[i].name, x);
188                             SendToProgram(buf, currentCps);
189                         } else *(int*) opts[i].target = x;
190                     }
191                     break;
192                 case CheckBox:
193                     j = 0;
194                     GetWidgetState(&opts[i], &j);
195                     if(opts[i].value != j) {
196                         opts[i].value = j;
197                         if(currentCps) {
198                             snprintf(buf, MSG_SIZ,  "option %s=%d\n", opts[i].name, j);
199                             SendToProgram(buf, currentCps);
200                         } else *(Boolean*) opts[i].target = j;
201                     }
202                     break;
203                 case ComboBox:
204                     if(opts[i].min & COMBO_CALLBACK) break;
205                     if(!opts[i].textValue) { *(int*)opts[i].target == opts[i].value; break; } // numeric
206                     val = ((char**)opts[i].textValue)[values[i]];
207                     if(currentCps) {
208                         if(opts[i].value == values[i]) break; // not changed
209                         opts[i].value = values[i];
210                         snprintf(buf, MSG_SIZ,  "option %s=%s\n", opts[i].name, opts[i].choice[values[i]]);
211                         SendToProgram(buf, currentCps);
212                     } else if(val && (*(char**) opts[i].target == NULL || strcmp(*(char**) opts[i].target, val))) {
213                       if(*(char**) opts[i].target) free(*(char**) opts[i].target);
214                       *(char**) opts[i].target = strdup(val);
215                     }
216                     break;
217                 case EndMark:
218                     if(opts[i].target) // callback for implementing necessary actions on OK (like redraw)
219                         res = ((OKCallback*) opts[i].target)(i);
220                     break;
221             default:
222                 printf("GenericReadout: unexpected case in switch.\n");
223                 case ListBox:
224                 case Button:
225                 case SaveButton:
226                 case Label:
227                 case Break:
228               break;
229             }
230             if(opts[i].type == EndMark) break;
231         }
232         return res;
233 }
234
235 //------------------------------------------- Match Options ------------------------------------------------------
236
237 char *engineName, *engineChoice, *tfName;
238 char *engineList[MAXENGINES] = {" "}, *engineMnemonic[MAXENGINES];
239
240 static void AddToTourney P((int n, int sel));
241 static void CloneTourney P((void));
242 static void ReplaceParticipant P((void));
243 static void UpgradeParticipant P((void));
244
245 static int
246 MatchOK (int n)
247 {
248     ASSIGN(appData.participants, engineName);
249     if(!CreateTourney(tfName) || matchMode) return matchMode || !appData.participants[0];
250     PopDown(TransientDlg); // early popdown to prevent FreezeUI called through MatchEvent from causing XtGrab warning
251     MatchEvent(2); // start tourney
252     return FALSE;  // no double PopDown!
253 }
254
255 static Option matchOptions[] = {
256 { 0,  0,          0, NULL, (void*) &tfName, ".trn", NULL, FileName, N_("Tournament file:") },
257 { 0,  0,          0, NULL, (void*) &appData.roundSync, "", NULL, CheckBox, N_("Sync after round") },
258 { 0, SAME_ROW|LL, 0, NULL, NULL, "", NULL, Label, N_("    (for concurrent playing of a single") },
259 { 0,  0,          0, NULL, (void*) &appData.cycleSync, "", NULL, CheckBox, N_("Sync after cycle") },
260 { 0, SAME_ROW|LL, 0, NULL, NULL, "", NULL, Label, N_("      tourney with multiple XBoards)") },
261 { 0,  LR,       200, NULL, NULL, "", NULL, Label, N_("Tourney participants:") },
262 { 0, SAME_ROW|RR, 0, NULL, NULL, "", NULL, Label, N_("Select Engine:") },
263 { 150, T_VSCRL | T_FILL | T_WRAP,
264                 175, NULL, (void*) &engineName, "", NULL, TextBox, "" },
265 { 150, SAME_ROW|RR,
266                 175, NULL, (void*) engineMnemonic, (char*) &AddToTourney, NULL, ListBox, "" },
267 //{ 0,  COMBO_CALLBACK | NO_GETTEXT,
268 //                0, NULL, (void*) &AddToTourney, (char*) (engineMnemonic+1), (engineMnemonic+1), ComboBox, N_("Select Engine:") },
269 { 0,  0,         10, NULL, (void*) &appData.tourneyType, "", NULL, Spin, N_("Tourney type (0 = round-robin, 1 = gauntlet):") },
270 { 0,  1, 1000000000, NULL, (void*) &appData.tourneyCycles, "", NULL, Spin, N_("Number of tourney cycles (or Swiss rounds):") },
271 { 0,  1, 1000000000, NULL, (void*) &appData.defaultMatchGames, "", NULL, Spin, N_("Default Number of Games in Match (or Pairing):") },
272 { 0,  0, 1000000000, NULL, (void*) &appData.matchPause, "", NULL, Spin, N_("Pause between Match Games (msec):") },
273 { 0,  0,          0, NULL, (void*) &appData.saveGameFile, ".pgn .game", NULL, FileName, N_("Save Tourney Games on:") },
274 { 0,  0,          0, NULL, (void*) &appData.loadGameFile, ".pgn .game", NULL, FileName, N_("Game File with Opening Lines:") },
275 { 0, -2, 1000000000, NULL, (void*) &appData.loadGameIndex, "", NULL, Spin, N_("Game Number (-1 or -2 = Auto-Increment):") },
276 { 0,  0,          0, NULL, (void*) &appData.loadPositionFile, ".fen .epd .pos", NULL, FileName, N_("File with Start Positions:") },
277 { 0, -2, 1000000000, NULL, (void*) &appData.loadPositionIndex, "", NULL, Spin, N_("Position Number (-1 or -2 = Auto-Increment):") },
278 { 0,  0, 1000000000, NULL, (void*) &appData.rewindIndex, "", NULL, Spin, N_("Rewind Index after this many Games (0 = never):") },
279 { 0,  0,          0, NULL, (void*) &appData.defNoBook, "", NULL, CheckBox, N_("Disable own engine books by default") },
280 { 0,  0,          0, NULL, (void*) &ReplaceParticipant, NULL, NULL, Button, N_("Replace Engine") },
281 { 0, SAME_ROW,    0, NULL, (void*) &UpgradeParticipant, NULL, NULL, Button, N_("Upgrade Engine") },
282 { 0, SAME_ROW,    0, NULL, (void*) &CloneTourney, NULL, NULL, Button, N_("Clone Tourney") },
283 { 0, SAME_ROW,    0, NULL, (void*) &MatchOK, "", NULL, EndMark , "" }
284 };
285
286 static void
287 ReplaceParticipant ()
288 {
289     GenericReadout(matchOptions, 7);
290     Substitute(strdup(engineName), True);
291 }
292
293 static void
294 UpgradeParticipant ()
295 {
296     GenericReadout(matchOptions, 7);
297     Substitute(strdup(engineName), False);
298 }
299
300 static void
301 CloneTourney ()
302 {
303     FILE *f;
304     char *name;
305     GetWidgetText(matchOptions, &name);
306     if(name && name[0] && (f = fopen(name, "r")) ) {
307         char *saveSaveFile;
308         saveSaveFile = appData.saveGameFile; appData.saveGameFile = NULL; // this is a persistent option, protect from change
309         ParseArgsFromFile(f);
310         engineName = appData.participants; GenericUpdate(matchOptions, -1);
311         FREE(appData.saveGameFile); appData.saveGameFile = saveSaveFile;
312     } else DisplayError(_("First you must specify an existing tourney file to clone"), 0);
313 }
314
315 static void
316 AddToTourney (int n, int sel)
317 {
318     int nr;
319     char buf[MSG_SIZ];
320     if(sel < 1) buf[0] = NULLCHAR; // back to top level
321     else if(engineList[sel][0] == '#') safeStrCpy(buf, engineList[sel], MSG_SIZ); // group header, open group
322     else { // normal line, select engine
323         AddLine(&matchOptions[7], engineMnemonic[sel]);
324         return;
325     }
326     nr = NamesToList(firstChessProgramNames, engineList, engineMnemonic, buf); // replace list by only the group contents
327     ASSIGN(engineMnemonic[0], buf);
328     LoadListBox(&matchOptions[8], _("# no engines are installed"));
329     HighlightWithScroll(&matchOptions[8], 0, nr);
330 }
331
332 void
333 MatchOptionsProc ()
334 {
335    NamesToList(firstChessProgramNames, engineList, engineMnemonic, "");
336    matchOptions[9].min = -(appData.pairingEngine[0] != NULLCHAR); // with pairing engine, allow Swiss
337    ASSIGN(tfName, appData.tourneyFile[0] ? appData.tourneyFile : MakeName(appData.defName));
338    ASSIGN(engineName, appData.participants);
339    ASSIGN(engineMnemonic[0], "");
340    GenericPopUp(matchOptions, _("Match Options"), TransientDlg, BoardWindow, MODAL, 0);
341 }
342
343 // ------------------------------------------- General Options --------------------------------------------------
344
345 static int oldShow, oldBlind, oldPonder;
346
347 static int
348 GeneralOptionsOK (int n)
349 {
350         int newPonder = appData.ponderNextMove;
351         appData.ponderNextMove = oldPonder;
352         PonderNextMoveEvent(newPonder);
353         if(!appData.highlightLastMove) ClearHighlights(), ClearPremoveHighlights();
354         if(oldShow != appData.showCoords || oldBlind != appData.blindfold) DrawPosition(TRUE, NULL);
355         return 1;
356 }
357
358 static Option generalOptions[] = {
359 { 0,  0, 0, NULL, (void*) &appData.whitePOV, "", NULL, CheckBox, N_("Absolute Analysis Scores") },
360 { 0,  0, 0, NULL, (void*) &appData.sweepSelect, "", NULL, CheckBox, N_("Almost Always Queen (Detour Under-Promote)") },
361 { 0,  0, 0, NULL, (void*) &appData.animateDragging, "", NULL, CheckBox, N_("Animate Dragging") },
362 { 0,  0, 0, NULL, (void*) &appData.animate, "", NULL, CheckBox, N_("Animate Moving") },
363 { 0,  0, 0, NULL, (void*) &appData.autoCallFlag, "", NULL, CheckBox, N_("Auto Flag") },
364 { 0,  0, 0, NULL, (void*) &appData.autoFlipView, "", NULL, CheckBox, N_("Auto Flip View") },
365 { 0,  0, 0, NULL, (void*) &appData.blindfold, "", NULL, CheckBox, N_("Blindfold") },
366 { 0,  0, 0, NULL, (void*) &appData.dropMenu, "", NULL, CheckBox, N_("Drop Menu") },
367 { 0,  0, 0, NULL, (void*) &appData.hideThinkingFromHuman, "", NULL, CheckBox, N_("Hide Thinking from Human") },
368 { 0,  0, 0, NULL, (void*) &appData.highlightLastMove, "", NULL, CheckBox, N_("Highlight Last Move") },
369 { 0,  0, 0, NULL, (void*) &appData.highlightMoveWithArrow, "", NULL, CheckBox, N_("Highlight with Arrow") },
370 { 0,  0, 0, NULL, (void*) &appData.ringBellAfterMoves, "", NULL, CheckBox, N_("Move Sound") },
371 { 0,  0, 0, NULL, (void*) &appData.oneClick, "", NULL, CheckBox, N_("One-Click Moving") },
372 { 0,  0, 0, NULL, (void*) &appData.periodicUpdates, "", NULL, CheckBox, N_("Periodic Updates (in Analysis Mode)") },
373 { 0,  0, 0, NULL, (void*) &appData.ponderNextMove, "", NULL, CheckBox, N_("Ponder Next Move") },
374 { 0,  0, 0, NULL, (void*) &appData.popupExitMessage, "", NULL, CheckBox, N_("Popup Exit Messages") },
375 { 0,  0, 0, NULL, (void*) &appData.popupMoveErrors, "", NULL, CheckBox, N_("Popup Move Errors") },
376 { 0,  0, 0, NULL, (void*) &appData.showEvalInMoveHistory, "", NULL, CheckBox, N_("Scores in Move List") },
377 { 0,  0, 0, NULL, (void*) &appData.showCoords, "", NULL, CheckBox, N_("Show Coordinates") },
378 { 0,  0, 0, NULL, (void*) &appData.markers, "", NULL, CheckBox, N_("Show Target Squares") },
379 { 0,  0, 0, NULL, (void*) &appData.useStickyWindows, "", NULL, CheckBox, N_("Sticky Windows") },
380 { 0,  0, 0, NULL, (void*) &appData.testLegality, "", NULL, CheckBox, N_("Test Legality") },
381 { 0,  0, 0, NULL, (void*) &appData.topLevel, "", NULL, CheckBox, N_("Top-Level Dialogs") },
382 { 0, 0,10,  NULL, (void*) &appData.flashCount, "", NULL, Spin, N_("Flash Moves (0 = no flashing):") },
383 { 0, 1,10,  NULL, (void*) &appData.flashRate, "", NULL, Spin, N_("Flash Rate (high = fast):") },
384 { 0, 5,100, NULL, (void*) &appData.animSpeed, "", NULL, Spin, N_("Animation Speed (high = slow):") },
385 { 0, 1,5,   NULL, (void*) &appData.zoom, "", NULL, Spin, N_("Zoom factor in Evaluation Graph:") },
386 { 0,  0, 0, NULL, (void*) &GeneralOptionsOK, "", NULL, EndMark , "" }
387 };
388
389 void
390 OptionsProc ()
391 {
392    oldPonder = appData.ponderNextMove;
393    oldShow = appData.showCoords; oldBlind = appData.blindfold;
394    GenericPopUp(generalOptions, _("General Options"), TransientDlg, BoardWindow, MODAL, 0);
395 }
396
397 //---------------------------------------------- New Variant ------------------------------------------------
398
399 static void Pick P((int n));
400
401 static char warning[MSG_SIZ];
402
403 static Option variantDescriptors[] = {
404 { 0, 0, 275, NULL, NULL, NULL, NULL, Label, warning },
405 { VariantNormal,        0, 135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_("normal")},
406 { VariantFairy,  SAME_ROW, 135, NULL, (void*) &Pick, "#BFBFBF", NULL, Button, N_("fairy")},
407 { VariantFischeRandom,  0, 135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_("FRC")},
408 { VariantSChess, SAME_ROW, 135, NULL, (void*) &Pick, "#FFBFBF", NULL, Button, N_("Seirawan")},
409 { VariantWildCastle,    0, 135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_("wild castle")},
410 { VariantSuper,  SAME_ROW, 135, NULL, (void*) &Pick, "#FFBFBF", NULL, Button, N_("Superchess")},
411 { VariantNoCastle,      0, 135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_("no castle")},
412 { VariantCrazyhouse,SAME_ROW,135,NULL,(void*) &Pick, "#FFBFBF", NULL, Button, N_("crazyhouse")},
413 { VariantKnightmate,    0, 135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_("knightmate")},
414 { VariantBughouse,SAME_ROW,135, NULL, (void*) &Pick, "#FFBFBF", NULL, Button, N_("bughouse")},
415 { VariantBerolina,      0, 135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_("berolina")},
416 { VariantShogi,  SAME_ROW, 135, NULL, (void*) &Pick, "#BFFFFF", NULL, Button, N_("shogi (9x9)")},
417 { VariantCylinder,      0, 135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_("cylinder")},
418 { VariantXiangqi, SAME_ROW,135, NULL, (void*) &Pick, "#BFFFFF", NULL, Button, N_("xiangqi (9x10)")},
419 { VariantShatranj,      0, 135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_("shatranj")},
420 { VariantCourier, SAME_ROW,135, NULL, (void*) &Pick, "#BFFFBF", NULL, Button, N_("courier (12x8)")},
421 { VariantMakruk,        0, 135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_("makruk")},
422 { VariantGreat,  SAME_ROW, 135, NULL, (void*) &Pick, "#BFBFFF", NULL, Button, N_("Great Shatranj (10x8)")},
423 { VariantAtomic,        0, 135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_("atomic")},
424 { VariantFalcon, SAME_ROW, 135, NULL, (void*) &Pick, "#BFBFFF", NULL, Button, N_("falcon (10x8)")},
425 { VariantTwoKings,      0, 135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_("two kings")},
426 { VariantCapablanca,SAME_ROW,135,NULL,(void*) &Pick, "#BFBFFF", NULL, Button, N_("Capablanca (10x8)")},
427 { Variant3Check,        0, 135, NULL, (void*) &Pick, "#FFFFFF", NULL, Button, N_("3-checks")},
428 { VariantGothic, SAME_ROW, 135, NULL, (void*) &Pick, "#BFBFFF", NULL, Button, N_("Gothic (10x8)")},
429 { VariantSuicide,       0, 135, NULL, (void*) &Pick, "#FFFFBF", NULL, Button, N_("suicide")},
430 { VariantJanus,  SAME_ROW, 135, NULL, (void*) &Pick, "#BFBFFF", NULL, Button, N_("janus (10x8)")},
431 { VariantGiveaway,      0, 135, NULL, (void*) &Pick, "#FFFFBF", NULL, Button, N_("give-away")},
432 { VariantCapaRandom,SAME_ROW,135,NULL,(void*) &Pick, "#BFBFFF", NULL, Button, N_("CRC (10x8)")},
433 { VariantLosers,        0, 135, NULL, (void*) &Pick, "#FFFFBF", NULL, Button, N_("losers")},
434 { VariantGrand,  SAME_ROW, 135, NULL, (void*) &Pick, "#5070FF", NULL, Button, N_("grand (10x10)")},
435 { VariantSpartan,       0, 135, NULL, (void*) &Pick, "#FF0000", NULL, Button, N_("Spartan")},
436 { 0, 0, 0, NULL, NULL, NULL, NULL, Label, N_("Board size ( -1 = default for selected variant):")},
437 { 0, -1, BOARD_RANKS-1, NULL, (void*) &appData.NrRanks, "", NULL, Spin, N_("Number of Board Ranks:") },
438 { 0, -1, BOARD_FILES, NULL, (void*) &appData.NrFiles, "", NULL, Spin, N_("Number of Board Files:") },
439 { 0, -1, BOARD_RANKS-1, NULL, (void*) &appData.holdingsSize, "", NULL, Spin, N_("Holdings Size:") },
440 { 0, 0, 0, NULL, NULL, NULL, NULL, Label,
441                                 N_("WARNING: variants with un-orthodox\n"
442                                   "pieces only have built-in bitmaps\n"
443                                   "for -boardSize middling, bulky and\n"
444                                   "petite, and substitute king or amazon\n"
445                                   "for missing bitmaps. (See manual.)")},
446 { 0, NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
447 };
448
449 static void
450 Pick (int n)
451 {
452         VariantClass v = variantDescriptors[n].value;
453         if(!appData.noChessProgram) {
454             char *name = VariantName(v), buf[MSG_SIZ];
455             if (first.protocolVersion > 1 && StrStr(first.variants, name) == NULL) {
456                 /* [HGM] in protocol 2 we check if variant is suported by engine */
457               snprintf(buf, MSG_SIZ,  _("Variant %s not supported by %s"), name, first.tidy);
458                 DisplayError(buf, 0);
459                 return; /* ignore OK if first engine does not support it */
460             } else
461             if (second.initDone && second.protocolVersion > 1 && StrStr(second.variants, name) == NULL) {
462               snprintf(buf, MSG_SIZ,  _("Warning: second engine (%s) does not support this!"), second.tidy);
463                 DisplayError(buf, 0);   /* use of second engine is optional; only warn user */
464             }
465         }
466
467         GenericReadout(variantDescriptors, -1); // make sure ranks and file settings are read
468
469         gameInfo.variant = v;
470         appData.variant = VariantName(v);
471
472         shuffleOpenings = FALSE; /* [HGM] shuffle: possible shuffle reset when we switch */
473         startedFromPositionFile = FALSE; /* [HGM] loadPos: no longer valid in new variant */
474         appData.pieceToCharTable = NULL;
475         appData.pieceNickNames = "";
476         appData.colorNickNames = "";
477         Reset(True, True);
478         PopDown(TransientDlg);
479         return;
480 }
481
482 void
483 NewVariantProc ()
484 {
485    sprintf(warning, _("All variants not supported by first engine\n(currently %s) are disabled"), first.tidy);
486    GenericPopUp(variantDescriptors, _("New Variant"), TransientDlg, BoardWindow, MODAL, 0);
487 }
488
489 //------------------------------------------- Common Engine Options -------------------------------------
490
491 static int oldCores;
492
493 static int
494 CommonOptionsOK (int n)
495 {
496         int newPonder = appData.ponderNextMove;
497         // make sure changes are sent to first engine by re-initializing it
498         // if it was already started pre-emptively at end of previous game
499         if(gameMode == BeginningOfGame) Reset(True, True); else {
500             // Some changed setting need immediate sending always.
501             if(oldCores != appData.smpCores)
502                 NewSettingEvent(False, &(first.maxCores), "cores", appData.smpCores);
503             appData.ponderNextMove = oldPonder;
504             PonderNextMoveEvent(newPonder);
505         }
506         return 1;
507 }
508
509 static Option commonEngineOptions[] = {
510 { 0,  0,    0, NULL, (void*) &appData.ponderNextMove, "", NULL, CheckBox, N_("Ponder Next Move") },
511 { 0,  0, 1000, NULL, (void*) &appData.smpCores, "", NULL, Spin, N_("Maximum Number of CPUs per Engine:") },
512 { 0,  0,    0, NULL, (void*) &appData.polyglotDir, "", NULL, PathName, N_("Polygot Directory:") },
513 { 0,  0,16000, NULL, (void*) &appData.defaultHashSize, "", NULL, Spin, N_("Hash-Table Size (MB):") },
514 { 0,  0,    0, NULL, (void*) &appData.defaultPathEGTB, "", NULL, PathName, N_("Nalimov EGTB Path:") },
515 { 0,  0, 1000, NULL, (void*) &appData.defaultCacheSizeEGTB, "", NULL, Spin, N_("EGTB Cache Size (MB):") },
516 { 0,  0,    0, NULL, (void*) &appData.usePolyglotBook, "", NULL, CheckBox, N_("Use GUI Book") },
517 { 0,  0,    0, NULL, (void*) &appData.polyglotBook, ".bin", NULL, FileName, N_("Opening-Book Filename:") },
518 { 0,  0,  100, NULL, (void*) &appData.bookDepth, "", NULL, Spin, N_("Book Depth (moves):") },
519 { 0,  0,  100, NULL, (void*) &appData.bookStrength, "", NULL, Spin, N_("Book Variety (0) vs. Strength (100):") },
520 { 0,  0,    0, NULL, (void*) &appData.firstHasOwnBookUCI, "", NULL, CheckBox, N_("Engine #1 Has Own Book") },
521 { 0,  0,    0, NULL, (void*) &appData.secondHasOwnBookUCI, "", NULL, CheckBox, N_("Engine #2 Has Own Book          ") },
522 { 0,SAME_ROW,0,NULL, (void*) &CommonOptionsOK, "", NULL, EndMark , "" }
523 };
524
525 void
526 UciMenuProc ()
527 {
528    oldCores = appData.smpCores;
529    oldPonder = appData.ponderNextMove;
530    GenericPopUp(commonEngineOptions, _("Common Engine Settings"), TransientDlg, BoardWindow, MODAL, 0);
531 }
532
533 //------------------------------------------ Adjudication Options --------------------------------------
534
535 static Option adjudicationOptions[] = {
536 { 0, 0,    0, NULL, (void*) &appData.checkMates, "", NULL, CheckBox, N_("Detect all Mates") },
537 { 0, 0,    0, NULL, (void*) &appData.testClaims, "", NULL, CheckBox, N_("Verify Engine Result Claims") },
538 { 0, 0,    0, NULL, (void*) &appData.materialDraws, "", NULL, CheckBox, N_("Draw if Insufficient Mating Material") },
539 { 0, 0,    0, NULL, (void*) &appData.trivialDraws, "", NULL, CheckBox, N_("Adjudicate Trivial Draws (3-Move Delay)") },
540 { 0, 0,100,   NULL, (void*) &appData.ruleMoves, "", NULL, Spin, N_("N-Move Rule:") },
541 { 0, 0,    6, NULL, (void*) &appData.drawRepeats, "", NULL, Spin, N_("N-fold Repeats:") },
542 { 0, 0,1000,  NULL, (void*) &appData.adjudicateDrawMoves, "", NULL, Spin, N_("Draw after N Moves Total:") },
543 { 0, -5000,0, NULL, (void*) &appData.adjudicateLossThreshold, "", NULL, Spin, N_("Win / Loss Threshold:") },
544 { 0, 0,    0, NULL, (void*) &first.scoreIsAbsolute, "", NULL, CheckBox, N_("Negate Score of Engine #1") },
545 { 0, 0,    0, NULL, (void*) &second.scoreIsAbsolute, "", NULL, CheckBox, N_("Negate Score of Engine #2") },
546 { 0,SAME_ROW, 0, NULL, NULL, "", NULL, EndMark , "" }
547 };
548
549 void
550 EngineMenuProc ()
551 {
552    GenericPopUp(adjudicationOptions, _("Adjudicate non-ICS Games"), TransientDlg, BoardWindow, MODAL, 0);
553 }
554
555 //--------------------------------------------- ICS Options ---------------------------------------------
556
557 static int
558 IcsOptionsOK (int n)
559 {
560     ParseIcsTextColors();
561     return 1;
562 }
563
564 Option icsOptions[] = {
565 { 0, 0, 0, NULL, (void*) &appData.autoKibitz, "",  NULL, CheckBox, N_("Auto-Kibitz") },
566 { 0, 0, 0, NULL, (void*) &appData.autoComment, "", NULL, CheckBox, N_("Auto-Comment") },
567 { 0, 0, 0, NULL, (void*) &appData.autoObserve, "", NULL, CheckBox, N_("Auto-Observe") },
568 { 0, 0, 0, NULL, (void*) &appData.autoRaiseBoard, "", NULL, CheckBox, N_("Auto-Raise Board") },
569 { 0, 0, 0, NULL, (void*) &appData.bgObserve, "",   NULL, CheckBox, N_("Background Observe while Playing") },
570 { 0, 0, 0, NULL, (void*) &appData.dualBoard, "",   NULL, CheckBox, N_("Dual Board for Background-Observed Game") },
571 { 0, 0, 0, NULL, (void*) &appData.getMoveList, "", NULL, CheckBox, N_("Get Move List") },
572 { 0, 0, 0, NULL, (void*) &appData.quietPlay, "",   NULL, CheckBox, N_("Quiet Play") },
573 { 0, 0, 0, NULL, (void*) &appData.seekGraph, "",   NULL, CheckBox, N_("Seek Graph") },
574 { 0, 0, 0, NULL, (void*) &appData.autoRefresh, "", NULL, CheckBox, N_("Auto-Refresh Seek Graph") },
575 { 0, 0, 0, NULL, (void*) &appData.premove, "",     NULL, CheckBox, N_("Premove") },
576 { 0, 0, 0, NULL, (void*) &appData.premoveWhite, "", NULL, CheckBox, N_("Premove for White") },
577 { 0, 0, 0, NULL, (void*) &appData.premoveWhiteText, "", NULL, TextBox, N_("First White Move:") },
578 { 0, 0, 0, NULL, (void*) &appData.premoveBlack, "", NULL, CheckBox, N_("Premove for Black") },
579 { 0, 0, 0, NULL, (void*) &appData.premoveBlackText, "", NULL, TextBox, N_("First Black Move:") },
580 { 0, SAME_ROW, 0, NULL, NULL, NULL, NULL, Break, "" },
581 { 0, 0, 0, NULL, (void*) &appData.icsAlarm, "", NULL, CheckBox, N_("Alarm") },
582 { 0, 0, 100000000, NULL, (void*) &appData.icsAlarmTime, "", NULL, Spin, N_("Alarm Time (msec):") },
583 //{ 0, 0, 0, NULL, (void*) &appData.chatBoxes, "", NULL, TextBox, N_("Startup Chat Boxes:") },
584 { 0, 0, 0, NULL, (void*) &appData.colorize, "", NULL, CheckBox, N_("Colorize Messages") },
585 { 0, 0, 0, NULL, (void*) &appData.colorShout, "", NULL, TextBox, N_("Shout Text Colors:") },
586 { 0, 0, 0, NULL, (void*) &appData.colorSShout, "", NULL, TextBox, N_("S-Shout Text Colors:") },
587 { 0, 0, 0, NULL, (void*) &appData.colorChannel1, "", NULL, TextBox, N_("Channel #1 Text Colors:") },
588 { 0, 0, 0, NULL, (void*) &appData.colorChannel, "", NULL, TextBox, N_("Other Channel Text Colors:") },
589 { 0, 0, 0, NULL, (void*) &appData.colorKibitz, "", NULL, TextBox, N_("Kibitz Text Colors:") },
590 { 0, 0, 0, NULL, (void*) &appData.colorTell, "", NULL, TextBox, N_("Tell Text Colors:") },
591 { 0, 0, 0, NULL, (void*) &appData.colorChallenge, "", NULL, TextBox, N_("Challenge Text Colors:") },
592 { 0, 0, 0, NULL, (void*) &appData.colorRequest, "", NULL, TextBox, N_("Request Text Colors:") },
593 { 0, 0, 0, NULL, (void*) &appData.colorSeek, "", NULL, TextBox, N_("Seek Text Colors:") },
594 { 0, 0, 0, NULL, (void*) &IcsOptionsOK, "", NULL, EndMark , "" }
595 };
596
597 void
598 IcsOptionsProc ()
599 {
600    GenericPopUp(icsOptions, _("ICS Options"), TransientDlg, BoardWindow, MODAL, 0);
601 }
602
603 //-------------------------------------------- Load Game Options ---------------------------------
604
605 static char *modeNames[] = { N_("Exact position match"), N_("Shown position is subset"), N_("Same material with exactly same Pawn chain"), 
606                       N_("Same material"), N_("Material range (top board half optional)"), N_("Material difference (optional stuff balanced)"), NULL };
607 static char *modeValues[] = { "1", "2", "3", "4", "5", "6" };
608 static char *searchMode;
609
610 static int
611 LoadOptionsOK ()
612 {
613     appData.searchMode = atoi(searchMode);
614     return 1;
615 }
616
617 static Option loadOptions[] = {
618 { 0,  0, 0,     NULL, (void*) &appData.autoDisplayTags, "", NULL, CheckBox, N_("Auto-Display Tags") },
619 { 0,  0, 0,     NULL, (void*) &appData.autoDisplayComment, "", NULL, CheckBox, N_("Auto-Display Comment") },
620 { 0, LR, 0,     NULL, NULL, NULL, NULL, Label, N_("Auto-Play speed of loaded games\n(0 = instant, -1 = off):") },
621 { 0, -1,10000000, NULL, (void*) &appData.timeDelay, "", NULL, Fractional, N_("Seconds per Move:") },
622 { 0, LR, 0,     NULL, NULL, NULL, NULL, Label,  N_("\noptions to use in game-viewer mode:") },
623 { 0, 0,300,     NULL, (void*) &appData.viewerOptions, "", NULL, TextBox,  "" },
624 { 0, LR,  0,    NULL, NULL, NULL, NULL, Label,  N_("\nThresholds for position filtering in game list:") },
625 { 0, 0,5000,    NULL, (void*) &appData.eloThreshold1, "", NULL, Spin, N_("Elo of strongest player at least:") },
626 { 0, 0,5000,    NULL, (void*) &appData.eloThreshold2, "", NULL, Spin, N_("Elo of weakest player at least:") },
627 { 0, 0,5000,    NULL, (void*) &appData.dateThreshold, "", NULL, Spin, N_("No games before year:") },
628 { 0, 1,50,      NULL, (void*) &appData.stretch, "", NULL, Spin, N_("Minimum nr consecutive positions:") },
629 { 0, 0,205,     NULL, (void*) &searchMode, (char*) modeValues, modeNames, ComboBox, N_("Search mode:") },
630 { 0, 0, 0,      NULL, (void*) &appData.ignoreColors, "", NULL, CheckBox, N_("Also match reversed colors") },
631 { 0, 0, 0,      NULL, (void*) &appData.findMirror, "", NULL, CheckBox, N_("Also match left-right flipped position") },
632 { 0,  0, 0,     NULL, (void*) &LoadOptionsOK, "", NULL, EndMark , "" }
633 };
634
635 void
636 LoadOptionsPopUp (DialogClass parent)
637 {
638    ASSIGN(searchMode, modeValues[appData.searchMode-1]);
639    GenericPopUp(loadOptions, _("Load Game Options"), TransientDlg, parent, MODAL, 0);
640 }
641
642 void
643 LoadOptionsProc ()
644 {   // called from menu
645     LoadOptionsPopUp(BoardWindow);
646 }
647
648 //------------------------------------------- Save Game Options --------------------------------------------
649
650 static Option saveOptions[] = {
651 { 0, 0, 0, NULL, (void*) &appData.autoSaveGames, "", NULL, CheckBox, N_("Auto-Save Games") },
652 { 0, 0, 0, NULL, (void*) &appData.saveGameFile, ".pgn", NULL, FileName,  N_("Save Games on File:") },
653 { 0, 0, 0, NULL, (void*) &appData.savePositionFile, ".fen", NULL, FileName,  N_("Save Final Positions on File:") },
654 { 0, 0, 0, NULL, (void*) &appData.pgnEventHeader, "", NULL, TextBox,  N_("PGN Event Header:") },
655 { 0, 0, 0, NULL, (void*) &appData.oldSaveStyle, "", NULL, CheckBox, N_("Old Save Style (as opposed to PGN)") },
656 { 0, 0, 0, NULL, (void*) &appData.numberTag, "", NULL, CheckBox, N_("Include Number Tag in tourney PGN") },
657 { 0, 0, 0, NULL, (void*) &appData.saveExtendedInfoInPGN, "", NULL, CheckBox, N_("Save Score/Depth Info in PGN") },
658 { 0, 0, 0, NULL, (void*) &appData.saveOutOfBookInfo, "", NULL, CheckBox, N_("Save Out-of-Book Info in PGN           ") },
659 { 0, SAME_ROW, 0, NULL, NULL, "", NULL, EndMark , "" }
660 };
661
662 void
663 SaveOptionsProc ()
664 {
665    GenericPopUp(saveOptions, _("Save Game Options"), TransientDlg, BoardWindow, MODAL, 0);
666 }
667
668 //----------------------------------------------- Sound Options ---------------------------------------------
669
670 static void Test P((int n));
671 static char *trialSound;
672
673 static char *soundNames[] = {
674         N_("No Sound"),
675         N_("Default Beep"),
676         N_("Above WAV File"),
677         N_("Car Horn"),
678         N_("Cymbal"),
679         N_("Ding"),
680         N_("Gong"),
681         N_("Laser"),
682         N_("Penalty"),
683         N_("Phone"),
684         N_("Pop"),
685         N_("Slap"),
686         N_("Wood Thunk"),
687         NULL,
688         N_("User File")
689 };
690
691 static char *soundFiles[] = { // sound files corresponding to above names
692         "",
693         "$",
694         NULL, // kludge alert: as first thing in the dialog readout this is replaced with the user-given .WAV filename
695         "honkhonk.wav",
696         "cymbal.wav",
697         "ding1.wav",
698         "gong.wav",
699         "laser.wav",
700         "penalty.wav",
701         "phone.wav",
702         "pop2.wav",
703         "slap.wav",
704         "woodthunk.wav",
705         NULL,
706         NULL
707 };
708
709 static Option soundOptions[] = {
710 { 0, 0, 0, NULL, (void*) &appData.soundProgram, "", NULL, TextBox, N_("Sound Program:") },
711 { 0, 0, 0, NULL, (void*) &appData.soundDirectory, "", NULL, PathName, N_("Sounds Directory:") },
712 { 0, 0, 0, NULL, (void*) (soundFiles+2) /* kludge! */, ".wav", NULL, FileName, N_("User WAV File:") },
713 { 0, 0, 0, NULL, (void*) &trialSound, (char*) soundFiles, soundNames, ComboBox, N_("Try-Out Sound:") },
714 { 0, SAME_ROW, 0, NULL, (void*) &Test, NULL, NULL, Button, N_("Play") },
715 { 0, 0, 0, NULL, (void*) &appData.soundMove, (char*) soundFiles, soundNames, ComboBox, N_("Move:") },
716 { 0, 0, 0, NULL, (void*) &appData.soundIcsWin, (char*) soundFiles, soundNames, ComboBox, N_("Win:") },
717 { 0, 0, 0, NULL, (void*) &appData.soundIcsLoss, (char*) soundFiles, soundNames, ComboBox, N_("Lose:") },
718 { 0, 0, 0, NULL, (void*) &appData.soundIcsDraw, (char*) soundFiles, soundNames, ComboBox, N_("Draw:") },
719 { 0, 0, 0, NULL, (void*) &appData.soundIcsUnfinished, (char*) soundFiles, soundNames, ComboBox, N_("Unfinished:") },
720 { 0, 0, 0, NULL, (void*) &appData.soundIcsAlarm, (char*) soundFiles, soundNames, ComboBox, N_("Alarm:") },
721 { 0, 0, 0, NULL, (void*) &appData.soundShout, (char*) soundFiles, soundNames, ComboBox, N_("Shout:") },
722 { 0, 0, 0, NULL, (void*) &appData.soundSShout, (char*) soundFiles, soundNames, ComboBox, N_("S-Shout:") },
723 { 0, 0, 0, NULL, (void*) &appData.soundChannel, (char*) soundFiles, soundNames, ComboBox, N_("Channel:") },
724 { 0, 0, 0, NULL, (void*) &appData.soundChannel1, (char*) soundFiles, soundNames, ComboBox, N_("Channel 1:") },
725 { 0, 0, 0, NULL, (void*) &appData.soundTell, (char*) soundFiles, soundNames, ComboBox, N_("Tell:") },
726 { 0, 0, 0, NULL, (void*) &appData.soundKibitz, (char*) soundFiles, soundNames, ComboBox, N_("Kibitz:") },
727 { 0, 0, 0, NULL, (void*) &appData.soundChallenge, (char*) soundFiles, soundNames, ComboBox, N_("Challenge:") },
728 { 0, 0, 0, NULL, (void*) &appData.soundRequest, (char*) soundFiles, soundNames, ComboBox, N_("Request:") },
729 { 0, 0, 0, NULL, (void*) &appData.soundSeek, (char*) soundFiles, soundNames, ComboBox, N_("Seek:") },
730 { 0, SAME_ROW, 0, NULL, NULL, "", NULL, EndMark , "" }
731 };
732
733 static void
734 Test (int n)
735 {
736     GenericReadout(soundOptions, 2);
737     if(soundFiles[values[3]]) PlaySound(soundFiles[values[3]]);
738 }
739
740 void
741 SoundOptionsProc ()
742 {
743    free(soundFiles[2]);
744    soundFiles[2] = strdup("*");
745    GenericPopUp(soundOptions, _("Sound Options"), TransientDlg, BoardWindow, MODAL, 0);
746 }
747
748 //--------------------------------------------- Board Options --------------------------------------
749
750 static void DefColor P((int n));
751 static void AdjustColor P((int i));
752
753 static int
754 BoardOptionsOK (int n)
755 {
756     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap; else lineGap = defaultLineGap;
757     useImages = useImageSqs = 0;
758     InitDrawingParams();
759     InitDrawingSizes(-1, 0);
760     DrawPosition(True, NULL);
761     return 1;
762 }
763
764 static Option boardOptions[] = {
765 { 0,          0, 70, NULL, (void*) &appData.whitePieceColor, "", NULL, TextBox, N_("White Piece Color:") },
766 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#FFFFCC", Button, "      " },
767 /* TRANSLATORS: R = single letter for the color red */
768 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
769 /* TRANSLATORS: G = single letter for the color green */
770 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
771 /* TRANSLATORS: B = single letter for the color blue */
772 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
773 /* TRANSLATORS: D = single letter to make a color darker */
774 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
775 { 0,          0, 70, NULL, (void*) &appData.blackPieceColor, "", NULL, TextBox, N_("Black Piece Color:") },
776 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#202020", Button, "      " },
777 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
778 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
779 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
780 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
781 { 0,          0, 70, NULL, (void*) &appData.lightSquareColor, "", NULL, TextBox, N_("Light Square Color:") },
782 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#C8C365", Button, "      " },
783 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
784 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
785 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
786 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
787 { 0,          0, 70, NULL, (void*) &appData.darkSquareColor, "", NULL, TextBox, N_("Dark Square Color:") },
788 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#77A26D", Button, "      " },
789 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
790 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
791 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
792 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
793 { 0,          0, 70, NULL, (void*) &appData.highlightSquareColor, "", NULL, TextBox, N_("Highlight Color:") },
794 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#FFFF00", Button, "      " },
795 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
796 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
797 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
798 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
799 { 0,          0, 70, NULL, (void*) &appData.premoveHighlightColor, "", NULL, TextBox, N_("Premove Highlight Color:") },
800 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#FF0000", Button, "      " },
801 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
802 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
803 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
804 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
805 { 0, 0, 0, NULL, (void*) &appData.upsideDown, "", NULL, CheckBox, N_("Flip Pieces Shogi Style        (Colored buttons restore default)") },
806 //{ 0, 0, 0, NULL, (void*) &appData.allWhite, "", NULL, CheckBox, N_("Use Outline Pieces for Black") },
807 { 0, 0, 0, NULL, (void*) &appData.monoMode, "", NULL, CheckBox, N_("Mono Mode") },
808 { 0,-1, 5, NULL, (void*) &appData.overrideLineGap, "", NULL, Spin, N_("Line Gap ( -1 = default for board size):") },
809 { 0, 0, 0, NULL, (void*) &appData.useBitmaps, "", NULL, CheckBox, N_("Use Board Textures") },
810 { 0, 0, 0, NULL, (void*) &appData.liteBackTextureFile, ".xpm", NULL, FileName, N_("Light-Squares Texture File:") },
811 { 0, 0, 0, NULL, (void*) &appData.darkBackTextureFile, ".xpm", NULL, FileName, N_("Dark-Squares Texture File:") },
812 { 0, 0, 0, NULL, (void*) &appData.bitmapDirectory, "", NULL, PathName, N_("Directory with Bitmap Pieces:") },
813 { 0, 0, 0, NULL, (void*) &appData.pixmapDirectory, "", NULL, PathName, N_("Directory with Pixmap Pieces:") },
814 { 0, 0, 0, NULL, (void*) &BoardOptionsOK, "", NULL, EndMark , "" }
815 };
816
817 static void
818 SetColorText (int n, char *buf)
819 {
820     SetWidgetText(&boardOptions[n-1], buf, TransientDlg);
821     SetColor(buf, &boardOptions[n]);
822 }
823
824 static void
825 DefColor (int n)
826 {
827     SetColorText(n, (char*) boardOptions[n].choice);
828 }
829
830 void
831 RefreshColor (int source, int n)
832 {
833     int col, j, r, g, b, step = 10;
834     char *s, buf[MSG_SIZ]; // color string
835     GetWidgetText(&boardOptions[source], &s);
836     if(sscanf(s, "#%x", &col) != 1) return;   // malformed
837     b = col & 0xFF; g = col & 0xFF00; r = col & 0xFF0000;
838     switch(n) {
839         case 1: r += 0x10000*step;break;
840         case 2: g += 0x100*step;  break;
841         case 3: b += step;        break;
842         case 4: r -= 0x10000*step; g -= 0x100*step; b -= step; break;
843     }
844     if(r < 0) r = 0; if(g < 0) g = 0; if(b < 0) b = 0;
845     if(r > 0xFF0000) r = 0xFF0000; if(g > 0xFF00) g = 0xFF00; if(b > 0xFF) b = 0xFF;
846     col = r | g | b;
847     snprintf(buf, MSG_SIZ, "#%06x", col);
848     for(j=1; j<7; j++) if(buf[j] >= 'a') buf[j] -= 32; // capitalize
849     SetColorText(source+1, buf);
850 }
851
852 static void
853 AdjustColor (int i)
854 {
855     int n = boardOptions[i].value;
856     RefreshColor(i-n-1, n);
857 }
858
859 void
860 BoardOptionsProc ()
861 {
862    GenericPopUp(boardOptions, _("Board Options"), TransientDlg, BoardWindow, MODAL, 0);
863 }
864
865 //-------------------------------------------- ICS Text Menu Options ------------------------------
866
867 Option textOptions[100];
868 static void PutText P((char *text, int pos));
869
870 void
871 SendString (char *p)
872 {
873     char buf[MSG_SIZ], *q;
874     if(q = strstr(p, "$input")) {
875         if(!shellUp[TextMenuDlg]) return;
876         strncpy(buf, p, MSG_SIZ);
877         strncpy(buf + (q-p), q+6, MSG_SIZ-(q-p));
878         PutText(buf, q-p);
879         return;
880     }
881     snprintf(buf, MSG_SIZ, "%s\n", p);
882     SendToICS(buf);
883 }
884
885 void
886 IcsTextProc ()
887 {
888    int i=0, j;
889    char *p, *q, *r;
890    if((p = icsTextMenuString) == NULL) return;
891    do {
892         q = r = p; while(*p && *p != ';') p++;
893         for(j=0; j<p-q; j++) textOptions[i].name[j] = *r++;
894         textOptions[i].name[j++] = 0;
895         if(!*p) break;
896         if(*++p == '\n') p++; // optional linefeed after button-text terminating semicolon
897         q = p;
898         textOptions[i].choice = (char**) (r = textOptions[i].name + j);
899         while(*p && (*p != ';' || p[1] != '\n')) textOptions[i].name[j++] = *p++;
900         textOptions[i].name[j++] = 0;
901         if(*p) p += 2;
902         textOptions[i].max = 135;
903         textOptions[i].min = i&1;
904         textOptions[i].handle = NULL;
905         textOptions[i].target = &SendText;
906         textOptions[i].textValue = strstr(r, "$input") ? "#80FF80" : strstr(r, "$name") ? "#FF8080" : "#FFFFFF";
907         textOptions[i].type = Button;
908    } while(++i < 99 && *p);
909    if(i == 0) return;
910    textOptions[i].type = EndMark;
911    textOptions[i].target = NULL;
912    textOptions[i].min = 2;
913    MarkMenu("View.ICStextmenu", TextMenuDlg);
914    GenericPopUp(textOptions, _("ICS text menu"), TextMenuDlg, BoardWindow, NONMODAL, 1);
915 }
916
917 //---------------------------------------------------- Edit Comment -----------------------------------
918
919 static char *commentText;
920 static int commentIndex;
921 static void ClearComment P((int n));
922 static void SaveChanges P((int n));
923
924 static int
925 NewComCallback (int n)
926 {
927     ReplaceComment(commentIndex, commentText);
928     return 1;
929 }
930
931 Option commentOptions[] = {
932 { 200, T_VSCRL | T_FILL | T_WRAP | T_TOP, 250, NULL, (void*) &commentText, "", NULL, TextBox, "" },
933 { 0,     0,     50, NULL, (void*) &ClearComment, NULL, NULL, Button, N_("clear") },
934 { 0, SAME_ROW, 100, NULL, (void*) &SaveChanges, NULL, NULL, Button, N_("save changes") },
935 { 0, SAME_ROW,  0,  NULL, (void*) &NewComCallback, "", NULL, EndMark , "" }
936 };
937
938 static void
939 SaveChanges (int n)
940 {
941     GenericReadout(commentOptions, 0);
942     ReplaceComment(commentIndex, commentText);
943 }
944
945 static void
946 ClearComment (int n)
947 {
948     SetWidgetText(&commentOptions[0], "", CommentDlg);
949 }
950
951 void
952 NewCommentPopup (char *title, char *text, int index)
953 {
954     if(DialogExists(CommentDlg)) { // if already exists, alter title and content
955         SetDialogTitle(CommentDlg, title);
956         SetWidgetText(&commentOptions[0], text, CommentDlg);
957     }
958     if(commentText) free(commentText); commentText = strdup(text);
959     commentIndex = index;
960     MarkMenu("View.Comments", CommentDlg);
961     if(GenericPopUp(commentOptions, title, CommentDlg, BoardWindow, NONMODAL, 1))
962         AddHandler(&commentOptions[0], 1);
963 }
964
965 void
966 EditCommentProc ()
967 {
968     int j;
969     if (PopDown(CommentDlg)) { // popdown succesful
970 //      MarkMenuItem("Edit.EditComment", False);
971 //      MarkMenuItem("View.Comments", False);
972     } else // was not up
973         EditCommentEvent();
974 }
975
976 //------------------------------------------------------ Edit Tags ----------------------------------
977
978 static void changeTags P((int n));
979 static char *tagsText;
980
981 static int
982 NewTagsCallback (int n)
983 {
984     ReplaceTags(tagsText, &gameInfo);
985     return 1;
986 }
987
988 static Option tagsOptions[] = {
989 {   0,   0,   0, NULL, NULL, NULL, NULL, Label,  NULL },
990 { 200, T_VSCRL | T_FILL | T_WRAP | T_TOP, 200, NULL, (void*) &tagsText, "", NULL, TextBox, "" },
991 {   0,   0, 100, NULL, (void*) &changeTags, NULL, NULL, Button, N_("save changes") },
992 { 0,SAME_ROW, 0, NULL, (void*) &NewTagsCallback, "", NULL, EndMark , "" }
993 };
994
995 static void
996 changeTags (int n)
997 {
998     GenericReadout(tagsOptions, 1);
999     if(bookUp) SaveToBook(tagsText); else
1000     ReplaceTags(tagsText, &gameInfo);
1001 }
1002
1003 void
1004 NewTagsPopup (char *text, char *msg)
1005 {
1006     char *title = bookUp ? _("Edit book") : _("Tags");
1007
1008     if(DialogExists(TagsDlg)) { // if already exists, alter title and content
1009         SetWidgetText(&tagsOptions[1], text, TagsDlg);
1010         SetDialogTitle(TagsDlg, title);
1011     }
1012     if(tagsText) free(tagsText); tagsText = strdup(text);
1013     tagsOptions[0].name = msg;
1014     MarkMenu("View.Tags", TagsDlg);
1015     GenericPopUp(tagsOptions, title, TagsDlg, BoardWindow, NONMODAL, 1);
1016 }
1017
1018 //---------------------------------------------- ICS Input Box ----------------------------------
1019
1020 char *icsText;
1021
1022 // [HGM] code borrowed from winboard.c (which should thus go to backend.c!)
1023 #define HISTORY_SIZE 64
1024 static char *history[HISTORY_SIZE];
1025 static int histIn = 0, histP = 0;
1026
1027 static void
1028 SaveInHistory (char *cmd)
1029 {
1030   if (history[histIn] != NULL) {
1031     free(history[histIn]);
1032     history[histIn] = NULL;
1033   }
1034   if (*cmd == NULLCHAR) return;
1035   history[histIn] = StrSave(cmd);
1036   histIn = (histIn + 1) % HISTORY_SIZE;
1037   if (history[histIn] != NULL) {
1038     free(history[histIn]);
1039     history[histIn] = NULL;
1040   }
1041   histP = histIn;
1042 }
1043
1044 static char *
1045 PrevInHistory (char *cmd)
1046 {
1047   int newhp;
1048   if (histP == histIn) {
1049     if (history[histIn] != NULL) free(history[histIn]);
1050     history[histIn] = StrSave(cmd);
1051   }
1052   newhp = (histP - 1 + HISTORY_SIZE) % HISTORY_SIZE;
1053   if (newhp == histIn || history[newhp] == NULL) return NULL;
1054   histP = newhp;
1055   return history[histP];
1056 }
1057
1058 static char *
1059 NextInHistory ()
1060 {
1061   if (histP == histIn) return NULL;
1062   histP = (histP + 1) % HISTORY_SIZE;
1063   return history[histP];   
1064 }
1065 // end of borrowed code
1066
1067 Option boxOptions[] = {
1068 {  30,  0,  400, NULL, (void*) &icsText, "", NULL, TextBox, "" },
1069 {  0,SAME_ROW | NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
1070 };
1071
1072 void
1073 ICSInputSendText ()
1074 {
1075     char *val;
1076
1077     GetWidgetText(&boxOptions[0], &val);
1078     SaveInHistory(val);
1079     SendMultiLineToICS(val);
1080     SetWidgetText(&boxOptions[0], val, InputBoxDlg);
1081 }
1082
1083 void
1084 IcsKey (int n)
1085 {   // [HGM] input: let up-arrow recall previous line from history
1086     char *val;
1087
1088     if (!shellUp[InputBoxDlg]) return;
1089     switch(n) {
1090       case 0:
1091         ICSInputSendText();
1092         return;
1093       case 1:
1094         GetWidgetText(&boxOptions[0], &val);
1095         val = PrevInHistory(val);
1096         break;
1097       case -1:
1098         val = NextInHistory();
1099     }
1100     SetWidgetText(&boxOptions[0], val ? val : "", InputBoxDlg);
1101 }
1102
1103 static void
1104 PutText (char *text, int pos)
1105 {
1106     char buf[MSG_SIZ], *p;
1107
1108     if(strstr(text, "$add ") == text) {
1109         GetWidgetText(&boxOptions[0], &p);
1110         snprintf(buf, MSG_SIZ, "%s%s", p, text+5); text = buf;
1111         pos += strlen(p) - 5;
1112     }
1113     SetWidgetText(&boxOptions[0], text, TextMenuDlg);
1114     SetInsertPos(&boxOptions[0], pos);
1115 }
1116
1117 void
1118 ICSInputBoxPopUp ()
1119 {
1120     MarkMenu("View.ICSInputBox", InputBoxDlg);
1121     if(GenericPopUp(boxOptions, _("ICS input box"), InputBoxDlg, BoardWindow, NONMODAL, 0))
1122         AddHandler(&boxOptions[0], 3);
1123 }
1124
1125 void
1126 IcsInputBoxProc ()
1127 {
1128     if (!PopDown(InputBoxDlg)) ICSInputBoxPopUp();
1129 }
1130
1131 //--------------------------------------------- Move Type In ------------------------------------------
1132
1133 static int TypeInOK P((int n));
1134
1135 Option typeOptions[] = {
1136 { 30,  0,            400, NULL, (void*) &icsText, "", NULL, TextBox, "" },
1137 { 0, SAME_ROW | NO_OK, 0, NULL, (void*) &TypeInOK, "", NULL, EndMark , "" }
1138 };
1139
1140 static int
1141 TypeInOK (int n)
1142 {
1143     TypeInDoneEvent(icsText);
1144     return TRUE;
1145 }
1146
1147 void
1148 PopUpMoveDialog (char firstchar)
1149 {
1150     static char buf[2];
1151     buf[0] = firstchar; ASSIGN(icsText, buf);
1152     if(GenericPopUp(typeOptions, _("Type a move"), TransientDlg, BoardWindow, MODAL, 0))
1153         AddHandler(&typeOptions[0], 2);
1154 }
1155
1156 void
1157 BoxAutoPopUp (char *buf)
1158 {
1159         if(appData.icsActive) { // text typed to board in ICS mode: divert to ICS input box
1160             if(DialogExists(InputBoxDlg)) { // box already exists: append to current contents
1161                 char *p, newText[MSG_SIZ];
1162                 GetWidgetText(&boxOptions[0], &p);
1163                 snprintf(newText, MSG_SIZ, "%s%c", p, *buf);
1164                 SetWidgetText(&boxOptions[0], newText, InputBoxDlg);
1165                 if(shellUp[InputBoxDlg]) HardSetFocus (&boxOptions[0]); //why???
1166             } else icsText = buf; // box did not exist: make sure it pops up with char in it
1167             ICSInputBoxPopUp();
1168         } else PopUpMoveDialog(*buf);
1169 }
1170
1171 //------------------------------------------ Engine Settings ------------------------------------
1172
1173 void
1174 SettingsPopUp (ChessProgramState *cps)
1175 {
1176    currentCps = cps;
1177    GenericPopUp(cps->option, _("Engine Settings"), TransientDlg, BoardWindow, MODAL, 0);
1178 }
1179
1180 void
1181 FirstSettingsProc ()
1182 {
1183     SettingsPopUp(&first);
1184 }
1185
1186 void
1187 SecondSettingsProc ()
1188 {
1189    if(WaitForEngine(&second, SettingsMenuIfReady)) return;
1190    SettingsPopUp(&second);
1191 }
1192
1193 //----------------------------------------------- Load Engine --------------------------------------
1194
1195 char *engineDir, *engineLine, *nickName, *params;
1196 Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick, secondEng;
1197
1198 static void EngSel P((int n, int sel));
1199 static int InstallOK P((int n));
1200
1201 static Option installOptions[] = {
1202 {   0,LR|T2T, 0, NULL, NULL, NULL, NULL, Label, N_("Select engine from list:") },
1203 { 300,LR|TB,200, NULL, (void*) engineMnemonic, (char*) &EngSel, NULL, ListBox, "" },
1204 { 0,SAME_ROW, 0, NULL, NULL, NULL, NULL, Break, NULL },
1205 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("or specify one below:") },
1206 {   0,  0,    0, NULL, (void*) &nickName, NULL, NULL, TextBox, N_("Nickname (optional):") },
1207 {   0,  0,    0, NULL, (void*) &useNick, NULL, NULL, CheckBox, N_("Use nickname in PGN player tags of engine-engine games") },
1208 {   0,  0,    0, NULL, (void*) &engineDir, NULL, NULL, PathName, N_("Engine Directory:") },
1209 {   0,  0,    0, NULL, (void*) &engineName, NULL, NULL, FileName, N_("Engine Command:") },
1210 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("(Directory will be derived from engine path when empty)") },
1211 {   0,  0,    0, NULL, (void*) &isUCI, NULL, NULL, CheckBox, N_("UCI") },
1212 {   0,  0,    0, NULL, (void*) &v1, NULL, NULL, CheckBox, N_("WB protocol v1 (do not wait for engine features)") },
1213 {   0,  0,    0, NULL, (void*) &hasBook, NULL, NULL, CheckBox, N_("Must not use GUI book") },
1214 {   0,  0,    0, NULL, (void*) &addToList, NULL, NULL, CheckBox, N_("Add this engine to the list") },
1215 {   0,  0,    0, NULL, (void*) &storeVariant, NULL, NULL, CheckBox, N_("Force current variant with this engine") },
1216 {   0,  0,    0, NULL, (void*) &InstallOK, "", NULL, EndMark , "" }
1217 };
1218
1219 static int
1220 InstallOK (int n)
1221 {
1222     if(n && (n = SelectedListBoxItem(&installOptions[1])) > 0) { // called by pressing OK, and engine selected
1223         ASSIGN(engineLine, engineList[n]);
1224     }
1225     PopDown(TransientDlg); // early popdown, to allow FreezeUI to instate grab
1226     if(!secondEng) Load(&first, 0); else Load(&second, 1);
1227     return FALSE; // no double PopDown!
1228 }
1229
1230 static void
1231 EngSel (int n, int sel)
1232 {
1233     int nr;
1234     char buf[MSG_SIZ];
1235     if(sel < 1) buf[0] = NULLCHAR; // back to top level
1236     else if(engineList[sel][0] == '#') safeStrCpy(buf, engineList[sel], MSG_SIZ); // group header, open group
1237     else { // normal line, select engine
1238         ASSIGN(engineLine, engineList[sel]);
1239         InstallOK(0);
1240         return;
1241     }
1242     nr = NamesToList(firstChessProgramNames, engineList, engineMnemonic, buf); // replace list by only the group contents
1243     ASSIGN(engineMnemonic[0], buf);
1244     LoadListBox(&installOptions[1], _("# no engines are installed"));
1245     HighlightWithScroll(&installOptions[1], 0, nr);
1246 }
1247
1248 static void
1249 LoadEngineProc (int engineNr, char *title)
1250 {
1251    isUCI = storeVariant = v1 = useNick = False; addToList = hasBook = True; // defaults
1252    secondEng = engineNr;
1253    if(engineLine)   free(engineLine);   engineLine = strdup("");
1254    if(engineDir)    free(engineDir);    engineDir = strdup(".");
1255    if(nickName)     free(nickName);     nickName = strdup("");
1256    if(params)       free(params);       params = strdup("");
1257    ASSIGN(engineMnemonic[0], "");
1258    NamesToList(firstChessProgramNames, engineList, engineMnemonic, "");
1259    GenericPopUp(installOptions, title, TransientDlg, BoardWindow, MODAL, 0);
1260 }
1261
1262 void
1263 LoadEngine1Proc ()
1264 {
1265     LoadEngineProc (0, _("Load first engine"));
1266 }
1267
1268 void
1269 LoadEngine2Proc ()
1270 {
1271     LoadEngineProc (1, _("Load second engine"));
1272 }
1273
1274 //----------------------------------------------------- Edit Book -----------------------------------------
1275
1276 void
1277 EditBookProc ()
1278 {
1279     EditBookEvent();
1280 }
1281
1282 //--------------------------------------------------- New Shuffle Game ------------------------------
1283
1284 static void SetRandom P((int n));
1285
1286 static int
1287 ShuffleOK (int n)
1288 {
1289     ResetGameEvent();
1290     return 1;
1291 }
1292
1293 static Option shuffleOptions[] = {
1294   {   0,  0,   50, NULL, (void*) &shuffleOpenings, NULL, NULL, CheckBox, N_("shuffle") },
1295   { 0,-1,2000000000, NULL, (void*) &appData.defaultFrcPosition, "", NULL, Spin, N_("Start-position number:") },
1296   {   0,  0,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("randomize") },
1297   {   0,  SAME_ROW,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("pick fixed") },
1298   { 0,SAME_ROW, 0, NULL, (void*) &ShuffleOK, "", NULL, EndMark , "" }
1299 };
1300
1301 static void
1302 SetRandom (int n)
1303 {
1304     int r = n==2 ? -1 : random() & (1<<30)-1;
1305     char buf[MSG_SIZ];
1306     snprintf(buf, MSG_SIZ,  "%d", r);
1307     SetWidgetText(&shuffleOptions[1], buf, TransientDlg);
1308     SetWidgetState(&shuffleOptions[0], True);
1309 }
1310
1311 void
1312 ShuffleMenuProc ()
1313 {
1314     GenericPopUp(shuffleOptions, _("New Shuffle Game"), TransientDlg, BoardWindow, MODAL, 0);
1315 }
1316
1317 //------------------------------------------------------ Time Control -----------------------------------
1318
1319 static int TcOK P((int n));
1320 int tmpMoves, tmpTc, tmpInc, tmpOdds1, tmpOdds2, tcType;
1321
1322 static void
1323 ShowTC (int n)
1324 {
1325 }
1326
1327 static void SetTcType P((int n));
1328
1329 static char *
1330 Value (int n)
1331 {
1332         static char buf[MSG_SIZ];
1333         snprintf(buf, MSG_SIZ, "%d", n);
1334         return buf;
1335 }
1336
1337 static Option tcOptions[] = {
1338 {   0,  0,    0, NULL, (void*) &SetTcType, NULL, NULL, Button, N_("classical") },
1339 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("incremental") },
1340 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("fixed max") },
1341 {   0,  0,  200, NULL, (void*) &tmpMoves, NULL, NULL, Spin, N_("Moves per session:") },
1342 {   0,  0,10000, NULL, (void*) &tmpTc,    NULL, NULL, Spin, N_("Initial time (min):") },
1343 {   0, 0, 10000, NULL, (void*) &tmpInc,   NULL, NULL, Spin, N_("Increment or max (sec/move):") },
1344 {   0,  0,    0, NULL, NULL, NULL, NULL, Label, N_("Time-Odds factors:") },
1345 {   0,  1, 1000, NULL, (void*) &tmpOdds1, NULL, NULL, Spin, N_("Engine #1") },
1346 {   0,  1, 1000, NULL, (void*) &tmpOdds2, NULL, NULL, Spin, N_("Engine #2 / Human") },
1347 {   0,  0,    0, NULL, (void*) &TcOK, "", NULL, EndMark , "" }
1348 };
1349
1350 static int
1351 TcOK (int n)
1352 {
1353     char *tc;
1354     if(tcType == 0 && tmpMoves <= 0) return 0;
1355     if(tcType == 2 && tmpInc <= 0) return 0;
1356     GetWidgetText(&tcOptions[4], &tc); // get original text, in case it is min:sec
1357     searchTime = 0;
1358     switch(tcType) {
1359       case 0:
1360         if(!ParseTimeControl(tc, -1, tmpMoves)) return 0;
1361         appData.movesPerSession = tmpMoves;
1362         ASSIGN(appData.timeControl, tc);
1363         appData.timeIncrement = -1;
1364         break;
1365       case 1:
1366         if(!ParseTimeControl(tc, tmpInc, 0)) return 0;
1367         ASSIGN(appData.timeControl, tc);
1368         appData.timeIncrement = tmpInc;
1369         break;
1370       case 2:
1371         searchTime = tmpInc;
1372     }
1373     appData.firstTimeOdds = first.timeOdds = tmpOdds1;
1374     appData.secondTimeOdds = second.timeOdds = tmpOdds2;
1375     Reset(True, True);
1376     return 1;
1377 }
1378
1379 static void
1380 SetTcType (int n)
1381 {
1382     switch(tcType = n) {
1383       case 0:
1384         SetWidgetText(&tcOptions[3], Value(tmpMoves), TransientDlg);
1385         SetWidgetText(&tcOptions[4], Value(tmpTc), TransientDlg);
1386         SetWidgetText(&tcOptions[5], _("Unused"), TransientDlg);
1387         break;
1388       case 1:
1389         SetWidgetText(&tcOptions[3], _("Unused"), TransientDlg);
1390         SetWidgetText(&tcOptions[4], Value(tmpTc), TransientDlg);
1391         SetWidgetText(&tcOptions[5], Value(tmpInc), TransientDlg);
1392         break;
1393       case 2:
1394         SetWidgetText(&tcOptions[3], _("Unused"), TransientDlg);
1395         SetWidgetText(&tcOptions[4], _("Unused"), TransientDlg);
1396         SetWidgetText(&tcOptions[5], Value(tmpInc), TransientDlg);
1397     }
1398 }
1399
1400 void
1401 TimeControlProc ()
1402 {
1403    tmpMoves = appData.movesPerSession;
1404    tmpInc = appData.timeIncrement; if(tmpInc < 0) tmpInc = 0;
1405    tmpOdds1 = tmpOdds2 = 1; tcType = 0;
1406    tmpTc = atoi(appData.timeControl);
1407    GenericPopUp(tcOptions, _("Time Control"), TransientDlg, BoardWindow, MODAL, 0);
1408 }
1409
1410 //------------------------------- Ask Question -----------------------------------------
1411
1412 int SendReply P((int n));
1413 char pendingReplyPrefix[MSG_SIZ];
1414 ProcRef pendingReplyPR;
1415 char *answer;
1416
1417 Option askOptions[] = {
1418 { 0, 0, 0, NULL, NULL, NULL, NULL, Label,  NULL },
1419 { 0, 0, 0, NULL, (void*) &answer, "", NULL, TextBox, "" },
1420 { 0, 0, 0, NULL, (void*) &SendReply, "", NULL, EndMark , "" }
1421 };
1422
1423 int
1424 SendReply (int n)
1425 {
1426     char buf[MSG_SIZ];
1427     int err;
1428     char *reply=answer;
1429 //    GetWidgetText(&askOptions[1], &reply);
1430     safeStrCpy(buf, pendingReplyPrefix, sizeof(buf)/sizeof(buf[0]) );
1431     if (*buf) strncat(buf, " ", MSG_SIZ - strlen(buf) - 1);
1432     strncat(buf, reply, MSG_SIZ - strlen(buf) - 1);
1433     strncat(buf, "\n",  MSG_SIZ - strlen(buf) - 1);
1434     OutputToProcess(pendingReplyPR, buf, strlen(buf), &err); // does not go into debug file??? => bug
1435     if (err) DisplayFatalError(_("Error writing to chess program"), err, 0);
1436     return TRUE;
1437 }
1438
1439 void
1440 AskQuestion (char *title, char *question, char *replyPrefix, ProcRef pr)
1441 {
1442     safeStrCpy(pendingReplyPrefix, replyPrefix, sizeof(pendingReplyPrefix)/sizeof(pendingReplyPrefix[0]) );
1443     pendingReplyPR = pr;
1444     ASSIGN(answer, "");
1445     askOptions[0].name = question;
1446     if(GenericPopUp(askOptions, title, AskDlg, BoardWindow, MODAL, 0))
1447         AddHandler(&askOptions[1], 2);
1448 }
1449
1450 //---------------------------- Promotion Popup --------------------------------------
1451
1452 static int count;
1453
1454 static void PromoPick P((int n));
1455
1456 static Option promoOptions[] = {
1457 {   0,         0,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, "" },
1458 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, "" },
1459 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, "" },
1460 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, "" },
1461 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, "" },
1462 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, "" },
1463 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, "" },
1464 {   0, SAME_ROW | NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
1465 };
1466
1467 static void
1468 PromoPick (int n)
1469 {
1470     int promoChar = promoOptions[n+count].value;
1471
1472     PopDown(PromoDlg);
1473
1474     if (promoChar == 0) fromX = -1;
1475     if (fromX == -1) return;
1476
1477     if (! promoChar) {
1478         fromX = fromY = -1;
1479         ClearHighlights();
1480         return;
1481     }
1482     UserMoveEvent(fromX, fromY, toX, toY, promoChar);
1483
1484     if (!appData.highlightLastMove || gotPremove) ClearHighlights();
1485     if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
1486     fromX = fromY = -1;
1487 }
1488
1489 static void
1490 SetPromo (char *name, int nr, char promoChar)
1491 {
1492     safeStrCpy(promoOptions[nr].name, name, MSG_SIZ);
1493     promoOptions[nr].value = promoChar;
1494 }
1495
1496 void
1497 PromotionPopUp ()
1498 { // choice depends on variant: prepare dialog acordingly
1499   count = 7;
1500   SetPromo(_("Cancel"), --count, 0); // Beware: GenericPopUp cannot handle user buttons named "cancel" (lowe case)!
1501   if(gameInfo.variant != VariantShogi) {
1502     if (!appData.testLegality || gameInfo.variant == VariantSuicide ||
1503         gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove) ||
1504         gameInfo.variant == VariantGiveaway) {
1505       SetPromo(_("King"), --count, 'k');
1506     }
1507     if(gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove)) {
1508       SetPromo(_("Captain"), --count, 'c');
1509       SetPromo(_("Lieutenant"), --count, 'l');
1510       SetPromo(_("General"), --count, 'g');
1511       SetPromo(_("Warlord"), --count, 'w');
1512     } else {
1513       SetPromo(_("Knight"), --count, 'n');
1514       SetPromo(_("Bishop"), --count, 'b');
1515       SetPromo(_("Rook"), --count, 'r');
1516       if(gameInfo.variant == VariantCapablanca ||
1517          gameInfo.variant == VariantGothic ||
1518          gameInfo.variant == VariantCapaRandom) {
1519         SetPromo(_("Archbishop"), --count, 'a');
1520         SetPromo(_("Chancellor"), --count, 'c');
1521       }
1522       SetPromo(_("Queen"), --count, 'q');
1523     }
1524   } else // [HGM] shogi
1525   {
1526       SetPromo(_("Defer"), --count, '=');
1527       SetPromo(_("Promote"), --count, '+');
1528   }
1529   GenericPopUp(promoOptions + count, "Promotion", PromoDlg, BoardWindow, NONMODAL, 0);
1530 }
1531
1532 //---------------------------- Chat Windows ----------------------------------------------
1533
1534 void
1535 OutputChatMessage (int partner, char *mess)
1536 {
1537     return; // dummy
1538 }
1539
1540 //--------------------------------- Game-List options dialog ------------------------------------------
1541
1542 char *strings[LPUSERGLT_SIZE];
1543 int stringPtr;
1544
1545 void
1546 GLT_ClearList ()
1547 {
1548     strings[0] = NULL;
1549     stringPtr = 0;
1550 }
1551
1552 void
1553 GLT_AddToList (char *name)
1554 {
1555     strings[stringPtr++] = name;
1556     strings[stringPtr] = NULL;
1557 }
1558
1559 Boolean
1560 GLT_GetFromList (int index, char *name)
1561 {
1562   safeStrCpy(name, strings[index], MSG_SIZ);
1563   return TRUE;
1564 }
1565
1566 void
1567 GLT_DeSelectList ()
1568 {
1569 }
1570
1571 static void GLT_Button P((int n));
1572 static int GLT_OK P((int n));
1573
1574 static Option listOptions[] = {
1575 { 0, LR|TB,  200, NULL, (void*) strings, "", NULL, ListBox, "" },
1576 { 0,    0,     0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("factory") },
1577 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("up") },
1578 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("down") },
1579 { 0, SAME_ROW, 0, NULL, (void*) &GLT_OK, "", NULL, EndMark , "" }
1580 };
1581
1582 static int
1583 GLT_OK (int n)
1584 {
1585     GLT_ParseList();
1586     appData.gameListTags = strdup(lpUserGLT);
1587     return 1;
1588 }
1589
1590 static void
1591 GLT_Button (int n)
1592 {
1593     int index = SelectedListBoxItem (&listOptions[0]);
1594     char *p;
1595     if (index < 0) {
1596         DisplayError(_("No tag selected"), 0);
1597         return;
1598     }
1599     p = strings[index];
1600     if (n == 3) {
1601         if(index >= strlen(GLT_ALL_TAGS)) return;
1602         strings[index] = strings[index+1];
1603         strings[++index] = p;
1604     } else
1605     if (n == 2) {
1606         if(index == 0) return;
1607         strings[index] = strings[index-1];
1608         strings[--index] = p;
1609     } else
1610     if (n == 1) {
1611       safeStrCpy(lpUserGLT, GLT_DEFAULT_TAGS, LPUSERGLT_SIZE);
1612       GLT_TagsToList(lpUserGLT);
1613       index = 0;
1614       LoadListBox(&listOptions[0], "?"); // Note: the others don't need this, as the highlight switching redraws the change items
1615     }
1616     HighlightListBoxItem(&listOptions[0], index);
1617 }
1618
1619 void
1620 GameListOptionsPopUp (DialogClass parent)
1621 {
1622     safeStrCpy(lpUserGLT, appData.gameListTags, LPUSERGLT_SIZE);
1623     GLT_TagsToList(lpUserGLT);
1624
1625     GenericPopUp(listOptions, _("Game-list options"), TransientDlg, parent, MODAL, 0);
1626 }
1627
1628 void
1629 GameListOptionsProc ()
1630 {
1631     GameListOptionsPopUp(BoardWindow);
1632 }
1633
1634 //----------------------------- Error popup in various uses -----------------------------
1635
1636 /*
1637  * [HGM] Note:
1638  * XBoard has always had some pathologic behavior with multiple simultaneous error popups,
1639  * (which can occur even for modal popups when asynchrounous events, e.g. caused by engine, request a popup),
1640  * and this new implementation reproduces that as well:
1641  * Only the shell of the last instance is remembered in shells[ErrorDlg] (which replaces errorShell),
1642  * so that PopDowns ordered from the code always refer to that instance, and once that is down,
1643  * have no clue as to how to reach the others. For the Delete Window button calling PopDown this
1644  * has now been repaired, as the action routine assigned to it gets the shell passed as argument.
1645  */
1646
1647 int errorUp = False;
1648
1649 void
1650 ErrorPopDown ()
1651 {
1652     if (!errorUp) return;
1653     dialogError = errorUp = False;
1654     PopDown(ErrorDlg); PopDown(FatalDlg); // on explicit request we pop down any error dialog
1655     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
1656 }
1657
1658 static int
1659 ErrorOK (int n)
1660 {
1661     dialogError = errorUp = False;
1662     PopDown(n == 1 ? FatalDlg : ErrorDlg); // kludge: non-modal dialogs have one less (dummy) option
1663     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
1664     return FALSE; // prevent second Popdown !
1665 }
1666
1667 static Option errorOptions[] = {
1668 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // dummy option: will never be displayed
1669 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // textValue field will be set before popup
1670 { 0,NO_CANCEL,0, NULL, (void*) &ErrorOK, "", NULL, EndMark , "" }
1671 };
1672
1673 void
1674 ErrorPopUp (char *title, char *label, int modal)
1675 {
1676     errorUp = True;
1677     errorOptions[1].name = label;
1678     if(dialogError = shellUp[TransientDlg]) 
1679         GenericPopUp(errorOptions+1, title, FatalDlg, TransientDlg, MODAL, 0); // pop up as daughter of the transient dialog
1680     else
1681         GenericPopUp(errorOptions+modal, title, modal ? FatalDlg: ErrorDlg, BoardWindow, modal, 0); // kludge: option start address indicates modality
1682 }
1683
1684 void
1685 DisplayError (String message, int error)
1686 {
1687     char buf[MSG_SIZ];
1688
1689     if (error == 0) {
1690         if (appData.debugMode || appData.matchMode) {
1691             fprintf(stderr, "%s: %s\n", programName, message);
1692         }
1693     } else {
1694         if (appData.debugMode || appData.matchMode) {
1695             fprintf(stderr, "%s: %s: %s\n",
1696                     programName, message, strerror(error));
1697         }
1698         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
1699         message = buf;
1700     }
1701     ErrorPopUp(_("Error"), message, FALSE);
1702 }
1703
1704
1705 void
1706 DisplayMoveError (String message)
1707 {
1708     fromX = fromY = -1;
1709     ClearHighlights();
1710     DrawPosition(FALSE, NULL);
1711     if (appData.debugMode || appData.matchMode) {
1712         fprintf(stderr, "%s: %s\n", programName, message);
1713     }
1714     if (appData.popupMoveErrors) {
1715         ErrorPopUp(_("Error"), message, FALSE);
1716     } else {
1717         DisplayMessage(message, "");
1718     }
1719 }
1720
1721
1722 void
1723 DisplayFatalError (String message, int error, int status)
1724 {
1725     char buf[MSG_SIZ];
1726
1727     errorExitStatus = status;
1728     if (error == 0) {
1729         fprintf(stderr, "%s: %s\n", programName, message);
1730     } else {
1731         fprintf(stderr, "%s: %s: %s\n",
1732                 programName, message, strerror(error));
1733         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
1734         message = buf;
1735     }
1736     if (appData.popupExitMessage && boardWidget && XtIsRealized(boardWidget)) {
1737       ErrorPopUp(status ? _("Fatal Error") : _("Exiting"), message, TRUE);
1738     } else {
1739       ExitEvent(status);
1740     }
1741 }
1742
1743 void
1744 DisplayInformation (String message)
1745 {
1746     ErrorPopDown();
1747     ErrorPopUp(_("Information"), message, TRUE);
1748 }
1749
1750 void
1751 DisplayNote (String message)
1752 {
1753     ErrorPopDown();
1754     ErrorPopUp(_("Note"), message, FALSE);
1755 }
1756
1757 void
1758 DisplayTitle (char *text)
1759 {
1760     char title[MSG_SIZ];
1761     char icon[MSG_SIZ];
1762
1763     if (text == NULL) text = "";
1764
1765     if (*text != NULLCHAR) {
1766       safeStrCpy(icon, text, sizeof(icon)/sizeof(icon[0]) );
1767       safeStrCpy(title, text, sizeof(title)/sizeof(title[0]) );
1768     } else if (appData.icsActive) {
1769         snprintf(icon, sizeof(icon), "%s", appData.icsHost);
1770         snprintf(title, sizeof(title), "%s: %s", programName, appData.icsHost);
1771     } else if (appData.cmailGameName[0] != NULLCHAR) {
1772         snprintf(icon, sizeof(icon), "%s", "CMail");
1773         snprintf(title,sizeof(title), "%s: %s", programName, "CMail");
1774 #ifdef GOTHIC
1775     // [HGM] license: This stuff should really be done in back-end, but WinBoard already had a pop-up for it
1776     } else if (gameInfo.variant == VariantGothic) {
1777       safeStrCpy(icon,  programName, sizeof(icon)/sizeof(icon[0]) );
1778       safeStrCpy(title, GOTHIC,     sizeof(title)/sizeof(title[0]) );
1779 #endif
1780 #ifdef FALCON
1781     } else if (gameInfo.variant == VariantFalcon) {
1782       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
1783       safeStrCpy(title, FALCON, sizeof(title)/sizeof(title[0]) );
1784 #endif
1785     } else if (appData.noChessProgram) {
1786       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
1787       safeStrCpy(title, programName, sizeof(title)/sizeof(title[0]) );
1788     } else {
1789       safeStrCpy(icon, first.tidy, sizeof(icon)/sizeof(icon[0]) );
1790         snprintf(title,sizeof(title), "%s: %s", programName, first.tidy);
1791     }
1792     SetWindowTitle(text, title, icon);
1793 }
1794
1795 void
1796 DisplayWhiteClock (long timeRemaining, int highlight)
1797 {
1798     if(appData.noGUI) return;
1799     DisplayTimerLabel(11, _("White"), timeRemaining, highlight);
1800     if(highlight) SetClockIcon(0);
1801 }
1802
1803 void
1804 DisplayBlackClock (long timeRemaining, int highlight)
1805 {
1806     if(appData.noGUI) return;
1807     DisplayTimerLabel(12, _("Black"), timeRemaining, highlight);
1808     if(highlight) SetClockIcon(1);
1809 }
1810
1811 #define PAUSE_BUTTON "P"
1812 #define PIECE_MENU_SIZE 18
1813 static String pieceMenuStrings[2][PIECE_MENU_SIZE+1] = {
1814     { N_("White"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
1815       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
1816       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
1817       N_("Empty square"), N_("Clear board"), NULL },
1818     { N_("Black"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
1819       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
1820       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
1821       N_("Empty square"), N_("Clear board"), NULL }
1822 };
1823 /* must be in same order as pieceMenuStrings! */
1824 static ChessSquare pieceMenuTranslation[2][PIECE_MENU_SIZE] = {
1825     { WhitePlay, (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
1826         WhiteRook, WhiteQueen, WhiteKing, (ChessSquare) 0, WhiteAlfil,
1827         WhiteCannon, WhiteAngel, WhiteMarshall, (ChessSquare) 0,
1828         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
1829     { BlackPlay, (ChessSquare) 0, BlackPawn, BlackKnight, BlackBishop,
1830         BlackRook, BlackQueen, BlackKing, (ChessSquare) 0, BlackAlfil,
1831         BlackCannon, BlackAngel, BlackMarshall, (ChessSquare) 0,
1832         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
1833 };
1834
1835 #define DROP_MENU_SIZE 6
1836 static String dropMenuStrings[DROP_MENU_SIZE+1] = {
1837     "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"), N_("Queen"), NULL
1838   };
1839 /* must be in same order as dropMenuStrings! */
1840 static ChessSquare dropMenuTranslation[DROP_MENU_SIZE] = {
1841     (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
1842     WhiteRook, WhiteQueen
1843 };
1844
1845 // [HGM] experimental code to pop up window just like the main window, using GenercicPopUp
1846
1847 static Option *Exp P((int n, int x, int y));
1848 void MenuCallback P((int n));
1849 void SizeKludge P((int n));
1850
1851 static int pmFromX = -1, pmFromY = -1;
1852
1853 static void
1854 PMSelect (int n)
1855 {   // user callback for board context menus
1856     if (pmFromX < 0 || pmFromY < 0) return;
1857     if(n == 25) DropMenuEvent(dropMenuTranslation[values[n]], pmFromX, pmFromY);
1858     else EditPositionMenuEvent(pieceMenuTranslation[n-23][values[n]], pmFromX, pmFromY);
1859 }
1860
1861 int
1862 CCB (int n)
1863 {
1864     shiftKey = (ShiftKeys() & 3) != 0;
1865     ClockClick(n == 12);
1866 }
1867
1868 Option mainOptions[] = { // description of main window in terms of generic dialog creator
1869 { 0, 0xCA, 0, NULL, NULL, "", NULL, BoxBegin, "" }, // menu bar
1870   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("File") },
1871   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Edit") },
1872   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("View") },
1873   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Mode") },
1874   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Action") },
1875   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Engine") },
1876   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Options") },
1877   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Help") },
1878 { 0, 0, 0, NULL, (void*)&SizeKludge, "", NULL, BoxEnd, "" },
1879 { 0, LR|T2T|BORDER|SAME_ROW, 0, NULL, NULL, "", NULL, Label, "1" }, // optional title in window
1880 { 0, L2L|T2T,              200, NULL, (void*) &CCB, NULL, NULL, Label, "White" }, // white clock
1881 { 0, R2R|T2T|SAME_ROW,     200, NULL, (void*) &CCB, NULL, NULL, Label, "Black" }, // black clock
1882 { 0, LR|T2T|BORDER,        401, NULL, NULL, "", NULL, -1, "2" }, // backup for title in window (if no room for other)
1883 { 0, LR|T2T|BORDER,        270, NULL, NULL, "", NULL, Label, "message" }, // message field
1884 { 0, RR|TT|SAME_ROW,       125, NULL, NULL, "", NULL, BoxBegin, "" }, // (optional) button bar
1885   { 0,    0,     0, NULL, (void*) &ToStartEvent, NULL, NULL, Button, N_("<<") },
1886   { 0, SAME_ROW, 0, NULL, (void*) &BackwardEvent, NULL, NULL, Button, N_("<") },
1887   { 0, SAME_ROW, 0, NULL, (void*) &PauseEvent, NULL, NULL, Button, N_(PAUSE_BUTTON) },
1888   { 0, SAME_ROW, 0, NULL, (void*) &ForwardEvent, NULL, NULL, Button, N_(">") },
1889   { 0, SAME_ROW, 0, NULL, (void*) &ToEndEvent, NULL, NULL, Button, N_(">>") },
1890 { 0, 0, 0, NULL, NULL, "", NULL, BoxEnd, "" },
1891 { 401, LR|TT, 401, NULL, (char*) &Exp, NULL, NULL, Graph, "shadow board" }, // board
1892   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[0], PopUp, "menuW" },
1893   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[1], PopUp, "menuB" },
1894   { -1, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, dropMenuStrings, PopUp, "menuD" },
1895 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
1896 };
1897
1898 void
1899 SizeKludge (int n)
1900 {   // callback called by GenericPopUp immediately after sizing the menu bar
1901     int width = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
1902     int w = width - 44 - mainOptions[n].min;
1903     mainOptions[10].max = w; // width left behind menu bar
1904     if(w < 0.4*width) // if no reasonable amount of space for title, force small layout
1905         mainOptions[13].type = mainOptions[10].type, mainOptions[10].type = -1; 
1906 }
1907
1908 void
1909 MenuCallback (int n)
1910 {
1911     MenuProc *proc = (MenuProc *) (((MenuItem*)(mainOptions[n].choice))[values[n]].proc);
1912
1913     (proc)();
1914 }
1915
1916 static Option *
1917 Exp (int n, int x, int y)
1918 {
1919     static int but1, but3;
1920     int menuNr = -3;
1921
1922     if(n == 0) { // motion
1923         if(SeekGraphClick(Press, x, y, 1)) return NULL;
1924         if(but1 && !PromoScroll(x, y)) DragPieceMove(x, y);
1925         if(but3) MovePV(x, y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
1926         return NULL;
1927     }
1928     shiftKey = (ShiftKeys() & 3) != 0;
1929     switch(n) {
1930         case  1: LeftClick(Press,   x, y), but1 = 1; break;
1931         case -1: LeftClick(Release, x, y), but1 = 0; break;
1932         case  2: shiftKey = !shiftKey;
1933         case  3: menuNr = RightClick(Press,   x, y, &pmFromX, &pmFromY), but3 = 1; break;
1934         case -2: shiftKey = !shiftKey;
1935         case -3: menuNr = RightClick(Release, x, y, &pmFromX, &pmFromY), but3 = 0; break;
1936         case 10:
1937             DrawPosition(True, NULL);
1938             if(twoBoards) { // [HGM] dual: draw other board in other orientation
1939                 flipView = !flipView; partnerUp = !partnerUp;
1940                 DrawPosition(True, NULL);
1941                 flipView = !flipView; partnerUp = !partnerUp;
1942             }
1943         default:
1944             return NULL;
1945     }
1946
1947     switch(menuNr) {
1948       case 0: return &mainOptions[shiftKey ? 23: 24];
1949       case 1: SetupDropMenu(); return &mainOptions[25];
1950       case 2:
1951       case -1: ErrorPopDown();
1952       case -2:
1953       default: break; // -3, so no clicks caught
1954     }
1955     return NULL;
1956 }
1957
1958 Option *
1959 BoardPopUp (int squareSize, int lineGap, void *clockFontThingy)
1960 {
1961     extern Option *dialogOptions[];
1962     int i, size = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
1963     mainOptions[11].choice = (char**) clockFontThingy;
1964     mainOptions[12].choice = (char**) clockFontThingy;
1965     mainOptions[22].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
1966     mainOptions[22].max = mainOptions[13].max = size; // board size
1967     mainOptions[13].max = size - 2; // board title (subtract border!)
1968     mainOptions[12].max = mainOptions[11].max = size/2-3; // clock width
1969     mainOptions[14].max = appData.showButtonBar ? size-130 : size-2; // message
1970     mainOptions[0].max = size-40; // menu bar
1971     mainOptions[10].type = appData.titleInWindow ? Label : -1 ;
1972     if(!appData.showButtonBar) for(i=15; i<22; i++) mainOptions[i].type = -1;
1973     for(i=0; i<8; i++) mainOptions[i+1].choice = (char**) menuBar[i].mi;
1974     GenericPopUp(mainOptions, "XBoard", BoardWindow, BoardWindow, NONMODAL, 1);
1975     return mainOptions;
1976 }
1977
1978 void
1979 DisplayMessage (char *message, char *extMessage)
1980 {
1981   /* display a message in the message widget */
1982
1983   char buf[MSG_SIZ];
1984
1985   if (extMessage)
1986     {
1987       if (*message)
1988         {
1989           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
1990           message = buf;
1991         }
1992       else
1993         {
1994           message = extMessage;
1995         };
1996     };
1997
1998     safeStrCpy(lastMsg, message, MSG_SIZ); // [HGM] make available
1999
2000   /* need to test if messageWidget already exists, since this function
2001      can also be called during the startup, if for example a Xresource
2002      is not set up correctly */
2003   if(mainOptions[14].handle)
2004     SetWidgetLabel(&mainOptions[14], message);
2005
2006   return;
2007 }
2008
2009 //----------------------------------- File Browser -------------------------------
2010
2011 #ifdef HAVE_DIRENT_H
2012 #include <dirent.h>
2013 #else
2014 #include <sys/dir.h>
2015 #define dirent direct
2016 #endif
2017
2018 #include <sys/stat.h>
2019
2020 static ChessProgramState *savCps;
2021 static FILE **savFP;
2022 static char *fileName, *extFilter, *dirListing, *savMode, **namePtr;
2023 static int folderPtr, filePtr, oldVal, byExtension, extFlag;
2024 static char curDir[MSG_SIZ], title[MSG_SIZ], *folderList[1000], *fileList[1000];
2025
2026 static char *FileTypes[] = {
2027 "Chess Games",
2028 "Chess Positions",
2029 "Tournaments",
2030 "Opening Books",
2031 "Settings (*.ini)",
2032 "Log files",
2033 "All files",
2034 NULL,
2035 "PGN",
2036 "Old-Style Games",
2037 "FEN",
2038 "Old-Style Positions",
2039 NULL,
2040 NULL
2041 };
2042
2043 static char *Extensions[] = {
2044 ".pgn .game",
2045 ".fen .epd .pos",
2046 ".trn",
2047 ".bin",
2048 ".ini",
2049 ".log",
2050 "",
2051 "INVALID",
2052 ".pgn",
2053 ".game",
2054 ".fen",
2055 ".pos",
2056 NULL,
2057 ""
2058 };
2059
2060 void DirSelProc P((int n, int sel));
2061 void FileSelProc P((int n, int sel));
2062 void SetTypeFilter P((int n));
2063 int BrowseOK P((int n));
2064 void Switch P((int n));
2065
2066 Option browseOptions[] = {
2067 {   0,    LR|T2T,      500, NULL, NULL, NULL, NULL, Label, title },
2068 {   0,    L2L|T2T,     250, NULL, NULL, NULL, NULL, Label, N_("Directories:") },
2069 {   0,R2R|T2T|SAME_ROW,100, NULL, NULL, NULL, NULL, Label, N_("Files:") },
2070 {   0,R2R|T2T|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by name") },
2071 {   0,R2R|T2T|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by type") },
2072 { 300,    L2L|T2T,     250, NULL, (void*) folderList, (char*) &DirSelProc, NULL, ListBox, "" },
2073 { 300,R2R|T2T|SAME_ROW,250, NULL, (void*) fileList, (char*) &FileSelProc, NULL, ListBox, "" },
2074 {   0,       0,        350, NULL, (void*) &fileName, NULL, NULL, TextBox, N_("Filename:") },
2075 {   0, COMBO_CALLBACK, 150, NULL, (void*) &SetTypeFilter, NULL, FileTypes, ComboBox, N_("File type:") },
2076 {   0,    SAME_ROW,      0, NULL, (void*) &BrowseOK, "", NULL, EndMark , "" }
2077 };
2078
2079 int
2080 BrowseOK (int n)
2081 {
2082         if(!fileName[0]) { // it is enough to have a file selected
2083             if(browseOptions[6].textValue) { // kludge: if callback specified we browse for file
2084                 int sel = SelectedListBoxItem(&browseOptions[6]);
2085                 if(sel < 0 || sel >= filePtr) return FALSE;
2086                 ASSIGN(fileName, fileList[sel]);
2087             } else { // we browse for path
2088                 ASSIGN(fileName, curDir); // kludge: without callback we browse for path
2089             }
2090         }
2091         if(!fileName[0]) return FALSE; // refuse OK when no file
2092         if(!savMode[0]) { // browsing for name only (dialog Browse button)
2093                 snprintf(title, MSG_SIZ, "%s/%s", curDir, fileName);
2094                 SetWidgetText((Option*) savFP, title, TransientDlg);
2095                 currentCps = savCps; // could return to Engine Settings dialog!
2096                 return TRUE;
2097         }
2098         *savFP = fopen(fileName, savMode);
2099         if(*savFP == NULL) return FALSE; // refuse OK if file not openable
2100         ASSIGN(*namePtr, fileName);
2101         ScheduleDelayedEvent(DelayedLoad, 50);
2102         currentCps = savCps; // not sure this is ever non-null
2103         return TRUE;
2104 }
2105
2106 void
2107 FileSelProc (int n, int sel)
2108 {
2109     if(sel<0) return;
2110     ASSIGN(fileName, fileList[sel]);
2111     if(BrowseOK(0)) PopDown(BrowserDlg);
2112 }
2113
2114 int
2115 AlphaNumCompare (char *p, char *q)
2116 {
2117     while(*p) {
2118         if(isdigit(*p) && isdigit(*q) && atoi(p) != atoi(q))
2119              return (atoi(p) > atoi(q) ? 1 : -1);
2120         if(*p != *q) break;
2121         p++, q++;
2122     }
2123     if(*p == *q) return 0;
2124     return (*p > *q ? 1 : -1);
2125 }
2126
2127 int
2128 Comp (const void *s, const void *t)
2129 {
2130     char *p = *(char**) s, *q = *(char**) t;
2131     if(extFlag) {
2132         char *h; int r;
2133         while(h = strchr(p, '.')) p = h+1;
2134         if(p == *(char**) s) p = "";
2135         while(h = strchr(q, '.')) q = h+1;
2136         if(q == *(char**) t) q = "";
2137         r = AlphaNumCompare(p, q);
2138         if(r) return r;
2139     }
2140     return AlphaNumCompare( *(char**) s, *(char**) t );
2141 }
2142
2143 void
2144 ListDir (int pathFlag)
2145 {
2146         DIR *dir;
2147         struct dirent *dp;
2148         struct stat statBuf;
2149         static int lastFlag;
2150         char buf[MSG_SIZ];
2151
2152         if(pathFlag < 0) pathFlag = lastFlag;
2153         lastFlag = pathFlag;
2154         dir = opendir(".");
2155         getcwd(curDir, MSG_SIZ);
2156         snprintf(title, MSG_SIZ, "%s   %s", _("Contents of"), curDir);
2157         folderPtr = filePtr = 0; // clear listing
2158
2159         while (dp = readdir(dir)) { // pass 1: list foders
2160             char *s = dp->d_name, match;
2161             if(!stat(s, &statBuf) && S_ISDIR(statBuf.st_mode)) { // stat succeeds and tells us it is directory
2162                 if(s[0] == '.' && strcmp(s, "..")) continue; // suppress hidden, except ".."
2163                 ASSIGN(folderList[folderPtr], s); folderPtr++;
2164             } else if(!pathFlag) {
2165                 char *s = dp->d_name, match=0;
2166                 if(s[0] == '.') continue; // suppress hidden files
2167                 if(extFilter[0]) { // [HGM] filter on extension
2168                     char *p = extFilter, *q;
2169                     do {
2170                         if(q = strchr(p, ' ')) *q = 0;
2171                         if(strstr(s, p)) match++;
2172                         if(q) *q = ' ';
2173                     } while(q && (p = q+1));
2174                     if(!match) continue;
2175                 }
2176                 ASSIGN(fileList[filePtr], s); filePtr++;
2177             }
2178         }
2179         FREE(folderList[folderPtr]); folderList[folderPtr] = NULL;
2180         FREE(fileList[filePtr]); fileList[filePtr] = NULL;
2181         closedir(dir);
2182         extFlag = 0;         qsort((void*)folderList, folderPtr, sizeof(char*), &Comp);
2183         extFlag = byExtension; qsort((void*)fileList, filePtr, sizeof(char*), &Comp);
2184 }
2185
2186 void
2187 Refresh (int pathFlag)
2188 {
2189     ListDir(pathFlag); // and make new one
2190     LoadListBox(&browseOptions[5], "");
2191     LoadListBox(&browseOptions[6], "");
2192 }
2193
2194 void
2195 Switch (int n)
2196 {
2197     if(byExtension == (n == 4)) return;
2198     extFlag = byExtension = (n == 4);
2199     qsort((void*)fileList, filePtr, sizeof(char*), &Comp);
2200     LoadListBox(&browseOptions[6], "");
2201 }
2202
2203 void
2204 SetTypeFilter (int n)
2205 {
2206     int j = values[n];
2207     if(j == browseOptions[n].value) return; // no change
2208     browseOptions[n].value = j;
2209     SetWidgetLabel(&browseOptions[n], FileTypes[j]);
2210     ASSIGN(extFilter, Extensions[j]);
2211     Refresh(-1); // uses pathflag remembered by ListDir
2212     values[n] = oldVal; // do not disturb combo settings of underlying dialog
2213 }
2214
2215 void
2216 DirSelProc (int n, int sel)
2217 {
2218     if(!chdir(folderList[sel])) { // cd succeeded, so we are in new directory now
2219         Refresh(-1);
2220         SetWidgetLabel(&browseOptions[0], title);
2221     }
2222 }
2223
2224 FILE *
2225 Browse (DialogClass dlg, char *label, char *proposed, char *ext, Boolean pathFlag, char *mode, char **name, FILE **fp)
2226 {
2227     int j=0;
2228     savFP = fp; savMode = mode, namePtr = name, savCps = currentCps, oldVal = values[8]; // save params, for use in callback
2229     ASSIGN(extFilter, ext);
2230     ASSIGN(fileName, proposed ? proposed : "");
2231     for(j=0; Extensions[j]; j++) // look up actual value in list of possible values, to get selection nr
2232         if(extFilter && !strcmp(extFilter, Extensions[j])) break;
2233     if(Extensions[j] == NULL) { j++; ASSIGN(FileTypes[j], extFilter); }
2234     browseOptions[8].value = j;
2235     browseOptions[6].textValue = (char*) (pathFlag ? NULL : &FileSelProc); // disable file listbox during path browsing
2236     ListDir(pathFlag);
2237     currentCps = NULL;
2238     if(GenericPopUp(browseOptions, label, BrowserDlg, dlg, MODAL, 0)) {
2239     }
2240     SetWidgetLabel(&browseOptions[8], FileTypes[j]);
2241 }
2242
2243