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