b802c622c78005d065019c98098f5553f2a15971
[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 .game", NULL, FileName, N_("Save Tourney Games on:") },
274 { 0,  0,          0, NULL, (void*) &appData.loadGameFile, ".pgn .game", NULL, FileName, N_("Game File with Opening Lines:") },
275 { 0, -2, 1000000000, NULL, (void*) &appData.loadGameIndex, "", NULL, Spin, N_("Game Number (-1 or -2 = Auto-Increment):") },
276 { 0,  0,          0, NULL, (void*) &appData.loadPositionFile, ".fen .epd .pos", NULL, FileName, N_("File with Start Positions:") },
277 { 0, -2, 1000000000, NULL, (void*) &appData.loadPositionIndex, "", NULL, Spin, N_("Position Number (-1 or -2 = Auto-Increment):") },
278 { 0,  0, 1000000000, NULL, (void*) &appData.rewindIndex, "", NULL, Spin, N_("Rewind Index after this many Games (0 = never):") },
279 { 0,  0,          0, NULL, (void*) &appData.defNoBook, "", NULL, CheckBox, N_("Disable own engine books by default") },
280 { 0,  0,          0, NULL, (void*) &ReplaceParticipant, NULL, NULL, Button, N_("Replace Engine") },
281 { 0, SAME_ROW,    0, NULL, (void*) &UpgradeParticipant, NULL, NULL, Button, N_("Upgrade Engine") },
282 { 0, SAME_ROW,    0, NULL, (void*) &CloneTourney, NULL, NULL, Button, N_("Clone Tourney") },
283 { 0, SAME_ROW,    0, NULL, (void*) &MatchOK, "", NULL, EndMark , "" }
284 };
285
286 static void
287 ReplaceParticipant ()
288 {
289     GenericReadout(matchOptions, 7);
290     Substitute(strdup(engineName), True);
291 }
292
293 static void
294 UpgradeParticipant ()
295 {
296     GenericReadout(matchOptions, 7);
297     Substitute(strdup(engineName), False);
298 }
299
300 static void
301 CloneTourney ()
302 {
303     FILE *f;
304     char *name;
305     GetWidgetText(matchOptions, &name);
306     if(name && name[0] && (f = fopen(name, "r")) ) {
307         char *saveSaveFile;
308         saveSaveFile = appData.saveGameFile; appData.saveGameFile = NULL; // this is a persistent option, protect from change
309         ParseArgsFromFile(f);
310         engineName = appData.participants; GenericUpdate(matchOptions, -1);
311         FREE(appData.saveGameFile); appData.saveGameFile = saveSaveFile;
312     } else DisplayError(_("First you must specify an existing tourney file to clone"), 0);
313 }
314
315 static void
316 AddToTourney (int n)
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     int nr;
1222     char buf[MSG_SIZ];
1223     if(sel < 1) buf[0] = NULLCHAR; // back to top level
1224     else if(engineList[sel][0] == '#') safeStrCpy(buf, engineList[sel], MSG_SIZ); // group header, open group
1225     else { // normal line, select engine
1226         ASSIGN(engineLine, engineList[sel]);
1227         InstallOK(0);
1228         return;
1229     }
1230     nr = NamesToList(firstChessProgramNames, engineList, engineMnemonic, buf); // replace list by only the group contents
1231     ASSIGN(engineMnemonic[0], buf);
1232     LoadListBox(&installOptions[1], _("# no engines are installed"));
1233     HighlightWithScroll(&installOptions[1], 0, nr);
1234 }
1235
1236 static void
1237 LoadEngineProc (int engineNr, char *title)
1238 {
1239    isUCI = storeVariant = v1 = useNick = False; addToList = hasBook = True; // defaults
1240    secondEng = engineNr;
1241    if(engineLine)   free(engineLine);   engineLine = strdup("");
1242    if(engineDir)    free(engineDir);    engineDir = strdup(".");
1243    if(nickName)     free(nickName);     nickName = strdup("");
1244    if(params)       free(params);       params = strdup("");
1245    ASSIGN(engineMnemonic[0], "");
1246    NamesToList(firstChessProgramNames, engineList, engineMnemonic, "");
1247    GenericPopUp(installOptions, title, TransientDlg, BoardWindow, MODAL, 0);
1248 }
1249
1250 void
1251 LoadEngine1Proc ()
1252 {
1253     LoadEngineProc (0, _("Load first engine"));
1254 }
1255
1256 void
1257 LoadEngine2Proc ()
1258 {
1259     LoadEngineProc (1, _("Load second engine"));
1260 }
1261
1262 //----------------------------------------------------- Edit Book -----------------------------------------
1263
1264 void
1265 EditBookProc ()
1266 {
1267     EditBookEvent();
1268 }
1269
1270 //--------------------------------------------------- New Shuffle Game ------------------------------
1271
1272 static void SetRandom P((int n));
1273
1274 static int
1275 ShuffleOK (int n)
1276 {
1277     ResetGameEvent();
1278     return 1;
1279 }
1280
1281 static Option shuffleOptions[] = {
1282   {   0,  0,   50, NULL, (void*) &shuffleOpenings, NULL, NULL, CheckBox, N_("shuffle") },
1283   { 0,-1,2000000000, NULL, (void*) &appData.defaultFrcPosition, "", NULL, Spin, N_("Start-position number:") },
1284   {   0,  0,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("randomize") },
1285   {   0,  SAME_ROW,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("pick fixed") },
1286   { 0,SAME_ROW, 0, NULL, (void*) &ShuffleOK, "", NULL, EndMark , "" }
1287 };
1288
1289 static void
1290 SetRandom (int n)
1291 {
1292     int r = n==2 ? -1 : random() & (1<<30)-1;
1293     char buf[MSG_SIZ];
1294     snprintf(buf, MSG_SIZ,  "%d", r);
1295     SetWidgetText(&shuffleOptions[1], buf, TransientDlg);
1296     SetWidgetState(&shuffleOptions[0], True);
1297 }
1298
1299 void
1300 ShuffleMenuProc ()
1301 {
1302     GenericPopUp(shuffleOptions, _("New Shuffle Game"), TransientDlg, BoardWindow, MODAL, 0);
1303 }
1304
1305 //------------------------------------------------------ Time Control -----------------------------------
1306
1307 static int TcOK P((int n));
1308 int tmpMoves, tmpTc, tmpInc, tmpOdds1, tmpOdds2, tcType;
1309
1310 static void
1311 ShowTC (int n)
1312 {
1313 }
1314
1315 static void SetTcType P((int n));
1316
1317 static char *
1318 Value (int n)
1319 {
1320         static char buf[MSG_SIZ];
1321         snprintf(buf, MSG_SIZ, "%d", n);
1322         return buf;
1323 }
1324
1325 static Option tcOptions[] = {
1326 {   0,  0,    0, NULL, (void*) &SetTcType, NULL, NULL, Button, N_("classical") },
1327 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("incremental") },
1328 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("fixed max") },
1329 {   0,  0,  200, NULL, (void*) &tmpMoves, NULL, NULL, Spin, N_("Moves per session:") },
1330 {   0,  0,10000, NULL, (void*) &tmpTc,    NULL, NULL, Spin, N_("Initial time (min):") },
1331 {   0, 0, 10000, NULL, (void*) &tmpInc,   NULL, NULL, Spin, N_("Increment or max (sec/move):") },
1332 {   0,  0,    0, NULL, NULL, NULL, NULL, Label, N_("Time-Odds factors:") },
1333 {   0,  1, 1000, NULL, (void*) &tmpOdds1, NULL, NULL, Spin, N_("Engine #1") },
1334 {   0,  1, 1000, NULL, (void*) &tmpOdds2, NULL, NULL, Spin, N_("Engine #2 / Human") },
1335 {   0,  0,    0, NULL, (void*) &TcOK, "", NULL, EndMark , "" }
1336 };
1337
1338 static int
1339 TcOK (int n)
1340 {
1341     char *tc;
1342     if(tcType == 0 && tmpMoves <= 0) return 0;
1343     if(tcType == 2 && tmpInc <= 0) return 0;
1344     GetWidgetText(&tcOptions[4], &tc); // get original text, in case it is min:sec
1345     searchTime = 0;
1346     switch(tcType) {
1347       case 0:
1348         if(!ParseTimeControl(tc, -1, tmpMoves)) return 0;
1349         appData.movesPerSession = tmpMoves;
1350         ASSIGN(appData.timeControl, tc);
1351         appData.timeIncrement = -1;
1352         break;
1353       case 1:
1354         if(!ParseTimeControl(tc, tmpInc, 0)) return 0;
1355         ASSIGN(appData.timeControl, tc);
1356         appData.timeIncrement = tmpInc;
1357         break;
1358       case 2:
1359         searchTime = tmpInc;
1360     }
1361     appData.firstTimeOdds = first.timeOdds = tmpOdds1;
1362     appData.secondTimeOdds = second.timeOdds = tmpOdds2;
1363     Reset(True, True);
1364     return 1;
1365 }
1366
1367 static void
1368 SetTcType (int n)
1369 {
1370     switch(tcType = n) {
1371       case 0:
1372         SetWidgetText(&tcOptions[3], Value(tmpMoves), TransientDlg);
1373         SetWidgetText(&tcOptions[4], Value(tmpTc), TransientDlg);
1374         SetWidgetText(&tcOptions[5], _("Unused"), TransientDlg);
1375         break;
1376       case 1:
1377         SetWidgetText(&tcOptions[3], _("Unused"), TransientDlg);
1378         SetWidgetText(&tcOptions[4], Value(tmpTc), TransientDlg);
1379         SetWidgetText(&tcOptions[5], Value(tmpInc), TransientDlg);
1380         break;
1381       case 2:
1382         SetWidgetText(&tcOptions[3], _("Unused"), TransientDlg);
1383         SetWidgetText(&tcOptions[4], _("Unused"), TransientDlg);
1384         SetWidgetText(&tcOptions[5], Value(tmpInc), TransientDlg);
1385     }
1386 }
1387
1388 void
1389 TimeControlProc ()
1390 {
1391    tmpMoves = appData.movesPerSession;
1392    tmpInc = appData.timeIncrement; if(tmpInc < 0) tmpInc = 0;
1393    tmpOdds1 = tmpOdds2 = 1; tcType = 0;
1394    tmpTc = atoi(appData.timeControl);
1395    GenericPopUp(tcOptions, _("Time Control"), TransientDlg, BoardWindow, MODAL, 0);
1396 }
1397
1398 //------------------------------- Ask Question -----------------------------------------
1399
1400 int SendReply P((int n));
1401 char pendingReplyPrefix[MSG_SIZ];
1402 ProcRef pendingReplyPR;
1403 char *answer;
1404
1405 Option askOptions[] = {
1406 { 0, 0, 0, NULL, NULL, NULL, NULL, Label,  NULL },
1407 { 0, 0, 0, NULL, (void*) &answer, "", NULL, TextBox, "" },
1408 { 0, 0, 0, NULL, (void*) &SendReply, "", NULL, EndMark , "" }
1409 };
1410
1411 int
1412 SendReply (int n)
1413 {
1414     char buf[MSG_SIZ];
1415     int err;
1416     char *reply=answer;
1417 //    GetWidgetText(&askOptions[1], &reply);
1418     safeStrCpy(buf, pendingReplyPrefix, sizeof(buf)/sizeof(buf[0]) );
1419     if (*buf) strncat(buf, " ", MSG_SIZ - strlen(buf) - 1);
1420     strncat(buf, reply, MSG_SIZ - strlen(buf) - 1);
1421     strncat(buf, "\n",  MSG_SIZ - strlen(buf) - 1);
1422     OutputToProcess(pendingReplyPR, buf, strlen(buf), &err); // does not go into debug file??? => bug
1423     if (err) DisplayFatalError(_("Error writing to chess program"), err, 0);
1424     return TRUE;
1425 }
1426
1427 void
1428 AskQuestion (char *title, char *question, char *replyPrefix, ProcRef pr)
1429 {
1430     safeStrCpy(pendingReplyPrefix, replyPrefix, sizeof(pendingReplyPrefix)/sizeof(pendingReplyPrefix[0]) );
1431     pendingReplyPR = pr;
1432     ASSIGN(answer, "");
1433     askOptions[0].name = question;
1434     if(GenericPopUp(askOptions, title, AskDlg, BoardWindow, MODAL, 0))
1435         AddHandler(&askOptions[1], 2);
1436 }
1437
1438 //---------------------------- Promotion Popup --------------------------------------
1439
1440 static int count;
1441
1442 static void PromoPick P((int n));
1443
1444 static Option promoOptions[] = {
1445 {   0,         0,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, "" },
1446 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, "" },
1447 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, "" },
1448 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, "" },
1449 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, "" },
1450 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, "" },
1451 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, "" },
1452 {   0, SAME_ROW | NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
1453 };
1454
1455 static void
1456 PromoPick (int n)
1457 {
1458     int promoChar = promoOptions[n+count].value;
1459
1460     PopDown(PromoDlg);
1461
1462     if (promoChar == 0) fromX = -1;
1463     if (fromX == -1) return;
1464
1465     if (! promoChar) {
1466         fromX = fromY = -1;
1467         ClearHighlights();
1468         return;
1469     }
1470     UserMoveEvent(fromX, fromY, toX, toY, promoChar);
1471
1472     if (!appData.highlightLastMove || gotPremove) ClearHighlights();
1473     if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
1474     fromX = fromY = -1;
1475 }
1476
1477 static void
1478 SetPromo (char *name, int nr, char promoChar)
1479 {
1480     safeStrCpy(promoOptions[nr].name, name, MSG_SIZ);
1481     promoOptions[nr].value = promoChar;
1482 }
1483
1484 void
1485 PromotionPopUp ()
1486 { // choice depends on variant: prepare dialog acordingly
1487   count = 7;
1488   SetPromo(_("Cancel"), --count, 0); // Beware: GenericPopUp cannot handle user buttons named "cancel" (lowe case)!
1489   if(gameInfo.variant != VariantShogi) {
1490     if (!appData.testLegality || gameInfo.variant == VariantSuicide ||
1491         gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove) ||
1492         gameInfo.variant == VariantGiveaway) {
1493       SetPromo(_("King"), --count, 'k');
1494     }
1495     if(gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove)) {
1496       SetPromo(_("Captain"), --count, 'c');
1497       SetPromo(_("Lieutenant"), --count, 'l');
1498       SetPromo(_("General"), --count, 'g');
1499       SetPromo(_("Warlord"), --count, 'w');
1500     } else {
1501       SetPromo(_("Knight"), --count, 'n');
1502       SetPromo(_("Bishop"), --count, 'b');
1503       SetPromo(_("Rook"), --count, 'r');
1504       if(gameInfo.variant == VariantCapablanca ||
1505          gameInfo.variant == VariantGothic ||
1506          gameInfo.variant == VariantCapaRandom) {
1507         SetPromo(_("Archbishop"), --count, 'a');
1508         SetPromo(_("Chancellor"), --count, 'c');
1509       }
1510       SetPromo(_("Queen"), --count, 'q');
1511     }
1512   } else // [HGM] shogi
1513   {
1514       SetPromo(_("Defer"), --count, '=');
1515       SetPromo(_("Promote"), --count, '+');
1516   }
1517   GenericPopUp(promoOptions + count, "Promotion", PromoDlg, BoardWindow, NONMODAL, 0);
1518 }
1519
1520 //---------------------------- Chat Windows ----------------------------------------------
1521
1522 void
1523 OutputChatMessage (int partner, char *mess)
1524 {
1525     return; // dummy
1526 }
1527
1528 //--------------------------------- Game-List options dialog ------------------------------------------
1529
1530 char *strings[LPUSERGLT_SIZE];
1531 int stringPtr;
1532
1533 void
1534 GLT_ClearList ()
1535 {
1536     strings[0] = NULL;
1537     stringPtr = 0;
1538 }
1539
1540 void
1541 GLT_AddToList (char *name)
1542 {
1543     strings[stringPtr++] = name;
1544     strings[stringPtr] = NULL;
1545 }
1546
1547 Boolean
1548 GLT_GetFromList (int index, char *name)
1549 {
1550   safeStrCpy(name, strings[index], MSG_SIZ);
1551   return TRUE;
1552 }
1553
1554 void
1555 GLT_DeSelectList ()
1556 {
1557 }
1558
1559 static void GLT_Button P((int n));
1560 static int GLT_OK P((int n));
1561
1562 static Option listOptions[] = {
1563 { 0, LR|TB,  200, NULL, (void*) strings, "", NULL, ListBox, "" },
1564 { 0,    0,     0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("factory") },
1565 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("up") },
1566 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("down") },
1567 { 0, SAME_ROW, 0, NULL, (void*) &GLT_OK, "", NULL, EndMark , "" }
1568 };
1569
1570 static int
1571 GLT_OK (int n)
1572 {
1573     GLT_ParseList();
1574     appData.gameListTags = strdup(lpUserGLT);
1575     return 1;
1576 }
1577
1578 static void
1579 GLT_Button (int n)
1580 {
1581     int index = SelectedListBoxItem (&listOptions[0]);
1582     char *p;
1583     if (index < 0) {
1584         DisplayError(_("No tag selected"), 0);
1585         return;
1586     }
1587     p = strings[index];
1588     if (n == 3) {
1589         if(index >= strlen(GLT_ALL_TAGS)) return;
1590         strings[index] = strings[index+1];
1591         strings[++index] = p;
1592     } else
1593     if (n == 2) {
1594         if(index == 0) return;
1595         strings[index] = strings[index-1];
1596         strings[--index] = p;
1597     } else
1598     if (n == 1) {
1599       safeStrCpy(lpUserGLT, GLT_DEFAULT_TAGS, LPUSERGLT_SIZE);
1600       GLT_TagsToList(lpUserGLT);
1601       index = 0;
1602       LoadListBox(&listOptions[0], "?"); // Note: the others don't need this, as the highlight switching redraws the change items
1603     }
1604     HighlightListBoxItem(&listOptions[0], index);
1605 }
1606
1607 void
1608 GameListOptionsPopUp (DialogClass parent)
1609 {
1610     safeStrCpy(lpUserGLT, appData.gameListTags, LPUSERGLT_SIZE);
1611     GLT_TagsToList(lpUserGLT);
1612
1613     GenericPopUp(listOptions, _("Game-list options"), TransientDlg, parent, MODAL, 0);
1614 }
1615
1616 void
1617 GameListOptionsProc ()
1618 {
1619     GameListOptionsPopUp(BoardWindow);
1620 }
1621
1622 //----------------------------- Error popup in various uses -----------------------------
1623
1624 /*
1625  * [HGM] Note:
1626  * XBoard has always had some pathologic behavior with multiple simultaneous error popups,
1627  * (which can occur even for modal popups when asynchrounous events, e.g. caused by engine, request a popup),
1628  * and this new implementation reproduces that as well:
1629  * Only the shell of the last instance is remembered in shells[ErrorDlg] (which replaces errorShell),
1630  * so that PopDowns ordered from the code always refer to that instance, and once that is down,
1631  * have no clue as to how to reach the others. For the Delete Window button calling PopDown this
1632  * has now been repaired, as the action routine assigned to it gets the shell passed as argument.
1633  */
1634
1635 int errorUp = False;
1636
1637 void
1638 ErrorPopDown ()
1639 {
1640     if (!errorUp) return;
1641     dialogError = errorUp = False;
1642     PopDown(ErrorDlg); PopDown(FatalDlg); // on explicit request we pop down any error dialog
1643     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
1644 }
1645
1646 static int
1647 ErrorOK (int n)
1648 {
1649     dialogError = errorUp = False;
1650     PopDown(n == 1 ? FatalDlg : ErrorDlg); // kludge: non-modal dialogs have one less (dummy) option
1651     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
1652     return FALSE; // prevent second Popdown !
1653 }
1654
1655 static Option errorOptions[] = {
1656 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // dummy option: will never be displayed
1657 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // textValue field will be set before popup
1658 { 0,NO_CANCEL,0, NULL, (void*) &ErrorOK, "", NULL, EndMark , "" }
1659 };
1660
1661 void
1662 ErrorPopUp (char *title, char *label, int modal)
1663 {
1664     errorUp = True;
1665     errorOptions[1].name = label;
1666     if(dialogError = shellUp[TransientDlg]) 
1667         GenericPopUp(errorOptions+1, title, FatalDlg, TransientDlg, MODAL, 0); // pop up as daughter of the transient dialog
1668     else
1669         GenericPopUp(errorOptions+modal, title, modal ? FatalDlg: ErrorDlg, BoardWindow, modal, 0); // kludge: option start address indicates modality
1670 }
1671
1672 void
1673 DisplayError (String message, int error)
1674 {
1675     char buf[MSG_SIZ];
1676
1677     if (error == 0) {
1678         if (appData.debugMode || appData.matchMode) {
1679             fprintf(stderr, "%s: %s\n", programName, message);
1680         }
1681     } else {
1682         if (appData.debugMode || appData.matchMode) {
1683             fprintf(stderr, "%s: %s: %s\n",
1684                     programName, message, strerror(error));
1685         }
1686         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
1687         message = buf;
1688     }
1689     ErrorPopUp(_("Error"), message, FALSE);
1690 }
1691
1692
1693 void
1694 DisplayMoveError (String message)
1695 {
1696     fromX = fromY = -1;
1697     ClearHighlights();
1698     DrawPosition(FALSE, NULL);
1699     if (appData.debugMode || appData.matchMode) {
1700         fprintf(stderr, "%s: %s\n", programName, message);
1701     }
1702     if (appData.popupMoveErrors) {
1703         ErrorPopUp(_("Error"), message, FALSE);
1704     } else {
1705         DisplayMessage(message, "");
1706     }
1707 }
1708
1709
1710 void
1711 DisplayFatalError (String message, int error, int status)
1712 {
1713     char buf[MSG_SIZ];
1714
1715     errorExitStatus = status;
1716     if (error == 0) {
1717         fprintf(stderr, "%s: %s\n", programName, message);
1718     } else {
1719         fprintf(stderr, "%s: %s: %s\n",
1720                 programName, message, strerror(error));
1721         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
1722         message = buf;
1723     }
1724     if (appData.popupExitMessage && boardWidget && XtIsRealized(boardWidget)) {
1725       ErrorPopUp(status ? _("Fatal Error") : _("Exiting"), message, TRUE);
1726     } else {
1727       ExitEvent(status);
1728     }
1729 }
1730
1731 void
1732 DisplayInformation (String message)
1733 {
1734     ErrorPopDown();
1735     ErrorPopUp(_("Information"), message, TRUE);
1736 }
1737
1738 void
1739 DisplayNote (String message)
1740 {
1741     ErrorPopDown();
1742     ErrorPopUp(_("Note"), message, FALSE);
1743 }
1744
1745 void
1746 DisplayTitle (char *text)
1747 {
1748     char title[MSG_SIZ];
1749     char icon[MSG_SIZ];
1750
1751     if (text == NULL) text = "";
1752
1753     if (*text != NULLCHAR) {
1754       safeStrCpy(icon, text, sizeof(icon)/sizeof(icon[0]) );
1755       safeStrCpy(title, text, sizeof(title)/sizeof(title[0]) );
1756     } else if (appData.icsActive) {
1757         snprintf(icon, sizeof(icon), "%s", appData.icsHost);
1758         snprintf(title, sizeof(title), "%s: %s", programName, appData.icsHost);
1759     } else if (appData.cmailGameName[0] != NULLCHAR) {
1760         snprintf(icon, sizeof(icon), "%s", "CMail");
1761         snprintf(title,sizeof(title), "%s: %s", programName, "CMail");
1762 #ifdef GOTHIC
1763     // [HGM] license: This stuff should really be done in back-end, but WinBoard already had a pop-up for it
1764     } else if (gameInfo.variant == VariantGothic) {
1765       safeStrCpy(icon,  programName, sizeof(icon)/sizeof(icon[0]) );
1766       safeStrCpy(title, GOTHIC,     sizeof(title)/sizeof(title[0]) );
1767 #endif
1768 #ifdef FALCON
1769     } else if (gameInfo.variant == VariantFalcon) {
1770       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
1771       safeStrCpy(title, FALCON, sizeof(title)/sizeof(title[0]) );
1772 #endif
1773     } else if (appData.noChessProgram) {
1774       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
1775       safeStrCpy(title, programName, sizeof(title)/sizeof(title[0]) );
1776     } else {
1777       safeStrCpy(icon, first.tidy, sizeof(icon)/sizeof(icon[0]) );
1778         snprintf(title,sizeof(title), "%s: %s", programName, first.tidy);
1779     }
1780     SetWindowTitle(text, title, icon);
1781 }
1782
1783 void
1784 DisplayWhiteClock (long timeRemaining, int highlight)
1785 {
1786     if(appData.noGUI) return;
1787     DisplayTimerLabel(11, _("White"), timeRemaining, highlight);
1788     if(highlight) SetClockIcon(0);
1789 }
1790
1791 void
1792 DisplayBlackClock (long timeRemaining, int highlight)
1793 {
1794     if(appData.noGUI) return;
1795     DisplayTimerLabel(12, _("Black"), timeRemaining, highlight);
1796     if(highlight) SetClockIcon(1);
1797 }
1798
1799 #define PAUSE_BUTTON "P"
1800 #define PIECE_MENU_SIZE 18
1801 static String pieceMenuStrings[2][PIECE_MENU_SIZE+1] = {
1802     { N_("White"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
1803       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
1804       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
1805       N_("Empty square"), N_("Clear board"), NULL },
1806     { N_("Black"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
1807       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
1808       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
1809       N_("Empty square"), N_("Clear board"), NULL }
1810 };
1811 /* must be in same order as pieceMenuStrings! */
1812 static ChessSquare pieceMenuTranslation[2][PIECE_MENU_SIZE] = {
1813     { WhitePlay, (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
1814         WhiteRook, WhiteQueen, WhiteKing, (ChessSquare) 0, WhiteAlfil,
1815         WhiteCannon, WhiteAngel, WhiteMarshall, (ChessSquare) 0,
1816         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
1817     { BlackPlay, (ChessSquare) 0, BlackPawn, BlackKnight, BlackBishop,
1818         BlackRook, BlackQueen, BlackKing, (ChessSquare) 0, BlackAlfil,
1819         BlackCannon, BlackAngel, BlackMarshall, (ChessSquare) 0,
1820         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
1821 };
1822
1823 #define DROP_MENU_SIZE 6
1824 static String dropMenuStrings[DROP_MENU_SIZE+1] = {
1825     "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"), N_("Queen"), NULL
1826   };
1827 /* must be in same order as dropMenuStrings! */
1828 static ChessSquare dropMenuTranslation[DROP_MENU_SIZE] = {
1829     (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
1830     WhiteRook, WhiteQueen
1831 };
1832
1833 // [HGM] experimental code to pop up window just like the main window, using GenercicPopUp
1834
1835 static Option *Exp P((int n, int x, int y));
1836 void MenuCallback P((int n));
1837 void SizeKludge P((int n));
1838
1839 static int pmFromX = -1, pmFromY = -1;
1840
1841 static void
1842 PMSelect (int n)
1843 {   // user callback for board context menus
1844     if (pmFromX < 0 || pmFromY < 0) return;
1845     if(n == 25) DropMenuEvent(dropMenuTranslation[values[n]], pmFromX, pmFromY);
1846     else EditPositionMenuEvent(pieceMenuTranslation[n-23][values[n]], pmFromX, pmFromY);
1847 }
1848
1849 int
1850 CCB (int n)
1851 {
1852     shiftKey = (ShiftKeys() & 3) != 0;
1853     ClockClick(n == 12);
1854 }
1855
1856 Option mainOptions[] = { // description of main window in terms of generic dialog creator
1857 { 0, 0xCA, 0, NULL, NULL, "", NULL, BoxBegin, "" }, // menu bar
1858   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("File") },
1859   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Edit") },
1860   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("View") },
1861   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Mode") },
1862   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Action") },
1863   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Engine") },
1864   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Options") },
1865   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Help") },
1866 { 0, 0, 0, NULL, (void*)&SizeKludge, "", NULL, BoxEnd, "" },
1867 { 0, LR|T2T|BORDER|SAME_ROW, 0, NULL, NULL, "", NULL, Label, "1" }, // optional title in window
1868 { 0, L2L|T2T,              200, NULL, (void*) &CCB, NULL, NULL, Label, "White" }, // white clock
1869 { 0, R2R|T2T|SAME_ROW,     200, NULL, (void*) &CCB, NULL, NULL, Label, "Black" }, // black clock
1870 { 0, LR|T2T|BORDER,        401, NULL, NULL, "", NULL, -1, "2" }, // backup for title in window (if no room for other)
1871 { 0, LR|T2T|BORDER,        270, NULL, NULL, "", NULL, Label, "message" }, // message field
1872 { 0, RR|TT|SAME_ROW,       125, NULL, NULL, "", NULL, BoxBegin, "" }, // (optional) button bar
1873   { 0,    0,     0, NULL, (void*) &ToStartEvent, NULL, NULL, Button, N_("<<") },
1874   { 0, SAME_ROW, 0, NULL, (void*) &BackwardEvent, NULL, NULL, Button, N_("<") },
1875   { 0, SAME_ROW, 0, NULL, (void*) &PauseEvent, NULL, NULL, Button, N_(PAUSE_BUTTON) },
1876   { 0, SAME_ROW, 0, NULL, (void*) &ForwardEvent, NULL, NULL, Button, N_(">") },
1877   { 0, SAME_ROW, 0, NULL, (void*) &ToEndEvent, NULL, NULL, Button, N_(">>") },
1878 { 0, 0, 0, NULL, NULL, "", NULL, BoxEnd, "" },
1879 { 401, LR|TT, 401, NULL, (char*) &Exp, NULL, NULL, Graph, "shadow board" }, // board
1880   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[0], PopUp, "menuW" },
1881   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[1], PopUp, "menuB" },
1882   { -1, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, dropMenuStrings, PopUp, "menuD" },
1883 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
1884 };
1885
1886 void
1887 SizeKludge (int n)
1888 {   // callback called by GenericPopUp immediately after sizing the menu bar
1889     int width = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
1890     int w = width - 44 - mainOptions[n].min;
1891     mainOptions[10].max = w; // width left behind menu bar
1892     if(w < 0.4*width) // if no reasonable amount of space for title, force small layout
1893         mainOptions[13].type = mainOptions[10].type, mainOptions[10].type = -1; 
1894 }
1895
1896 void
1897 MenuCallback (int n)
1898 {
1899     MenuProc *proc = (MenuProc *) (((MenuItem*)(mainOptions[n].choice))[values[n]].proc);
1900
1901     (proc)();
1902 }
1903
1904 static Option *
1905 Exp (int n, int x, int y)
1906 {
1907     static int but1, but3;
1908     int menuNr = -3;
1909
1910     if(n == 0) { // motion
1911         if(SeekGraphClick(Press, x, y, 1)) return NULL;
1912         if(but1 && !PromoScroll(x, y)) DragPieceMove(x, y);
1913         if(but3) MovePV(x, y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
1914         return NULL;
1915     }
1916     shiftKey = (ShiftKeys() & 3) != 0;
1917     switch(n) {
1918         case  1: LeftClick(Press,   x, y), but1 = 1; break;
1919         case -1: LeftClick(Release, x, y), but1 = 0; break;
1920         case  2: shiftKey = !shiftKey;
1921         case  3: menuNr = RightClick(Press,   x, y, &pmFromX, &pmFromY), but3 = 1; break;
1922         case -2: shiftKey = !shiftKey;
1923         case -3: menuNr = RightClick(Release, x, y, &pmFromX, &pmFromY), but3 = 0; break;
1924         case 10:
1925             DrawPosition(True, NULL);
1926             if(twoBoards) { // [HGM] dual: draw other board in other orientation
1927                 flipView = !flipView; partnerUp = !partnerUp;
1928                 DrawPosition(True, NULL);
1929                 flipView = !flipView; partnerUp = !partnerUp;
1930             }
1931         default:
1932             return NULL;
1933     }
1934
1935     switch(menuNr) {
1936       case 0: return &mainOptions[shiftKey ? 23: 24];
1937       case 1: SetupDropMenu(); return &mainOptions[25];
1938       case 2:
1939       case -1: ErrorPopDown();
1940       case -2:
1941       default: break; // -3, so no clicks caught
1942     }
1943     return NULL;
1944 }
1945
1946 Option *
1947 BoardPopUp (int squareSize, int lineGap, void *clockFontThingy)
1948 {
1949     extern Option *dialogOptions[];
1950     int i, size = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
1951     mainOptions[11].choice = (char**) clockFontThingy;
1952     mainOptions[12].choice = (char**) clockFontThingy;
1953     mainOptions[22].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
1954     mainOptions[22].max = mainOptions[13].max = size; // board size
1955     mainOptions[13].max = size - 2; // board title (subtract border!)
1956     mainOptions[12].max = mainOptions[11].max = size/2-3; // clock width
1957     mainOptions[14].max = appData.showButtonBar ? size-130 : size-2; // message
1958     mainOptions[0].max = size-40; // menu bar
1959     mainOptions[10].type = appData.titleInWindow ? Label : -1 ;
1960     if(!appData.showButtonBar) for(i=15; i<22; i++) mainOptions[i].type = -1;
1961     for(i=0; i<8; i++) mainOptions[i+1].choice = (char**) menuBar[i].mi;
1962     GenericPopUp(mainOptions, "XBoard", BoardWindow, BoardWindow, NONMODAL, 1);
1963     return mainOptions;
1964 }
1965
1966 void
1967 DisplayMessage (char *message, char *extMessage)
1968 {
1969   /* display a message in the message widget */
1970
1971   char buf[MSG_SIZ];
1972
1973   if (extMessage)
1974     {
1975       if (*message)
1976         {
1977           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
1978           message = buf;
1979         }
1980       else
1981         {
1982           message = extMessage;
1983         };
1984     };
1985
1986     safeStrCpy(lastMsg, message, MSG_SIZ); // [HGM] make available
1987
1988   /* need to test if messageWidget already exists, since this function
1989      can also be called during the startup, if for example a Xresource
1990      is not set up correctly */
1991   if(mainOptions[14].handle)
1992     SetWidgetLabel(&mainOptions[14], message);
1993
1994   return;
1995 }
1996
1997 //----------------------------------- File Browser -------------------------------
1998
1999 #ifdef HAVE_DIRENT_H
2000 #include <dirent.h>
2001 #else
2002 #include <sys/dir.h>
2003 #define dirent direct
2004 #endif
2005
2006 #include <sys/stat.h>
2007
2008 static ChessProgramState *savCps;
2009 static FILE **savFP;
2010 static char *fileName, *extFilter, *dirListing, *savMode, **namePtr;
2011 static int folderPtr, filePtr, oldVal, byExtension, extFlag;
2012 static char curDir[MSG_SIZ], title[MSG_SIZ], *folderList[1000], *fileList[1000];
2013
2014 static char *FileTypes[] = {
2015 "Chess Games",
2016 "Chess Positions",
2017 "Tournaments",
2018 "Opening Books",
2019 "Settings (*.ini)",
2020 "Log files",
2021 "All files",
2022 NULL,
2023 "PGN",
2024 "Old-Style Games",
2025 "FEN",
2026 "Old-Style Positions",
2027 NULL,
2028 NULL
2029 };
2030
2031 static char *Extensions[] = {
2032 ".pgn .game",
2033 ".fen .epd .pos",
2034 ".trn",
2035 ".bin",
2036 ".ini",
2037 ".log",
2038 "",
2039 "INVALID",
2040 ".pgn",
2041 ".game",
2042 ".fen",
2043 ".pos",
2044 NULL,
2045 ""
2046 };
2047
2048 void DirSelProc P((int n, int sel));
2049 void FileSelProc P((int n, int sel));
2050 void SetTypeFilter P((int n));
2051 int BrowseOK P((int n));
2052 void Switch P((int n));
2053
2054 Option browseOptions[] = {
2055 {   0,    LR|T2T,      500, NULL, NULL, NULL, NULL, Label, title },
2056 {   0,    L2L|T2T,     250, NULL, NULL, NULL, NULL, Label, N_("Directories:") },
2057 {   0,R2R|T2T|SAME_ROW,100, NULL, NULL, NULL, NULL, Label, N_("Files:") },
2058 {   0,R2R|T2T|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by name") },
2059 {   0,R2R|T2T|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by type") },
2060 { 300,    L2L|T2T,     250, NULL, (void*) folderList, (char*) &DirSelProc, NULL, ListBox, "" },
2061 { 300,R2R|T2T|SAME_ROW,250, NULL, (void*) fileList, (char*) &FileSelProc, NULL, ListBox, "" },
2062 {   0,       0,        350, NULL, (void*) &fileName, NULL, NULL, TextBox, N_("Filename:") },
2063 {   0, COMBO_CALLBACK, 150, NULL, (void*) &SetTypeFilter, NULL, FileTypes, ComboBox, N_("File type:") },
2064 {   0,    SAME_ROW,      0, NULL, (void*) &BrowseOK, "", NULL, EndMark , "" }
2065 };
2066
2067 int
2068 BrowseOK (int n)
2069 {
2070         if(!fileName[0]) { // it is enough to have a file selected
2071             if(browseOptions[6].textValue) { // kludge: if callback specified we browse for file
2072                 int sel = SelectedListBoxItem(&browseOptions[6]);
2073                 if(sel < 0 || sel >= filePtr) return FALSE;
2074                 ASSIGN(fileName, fileList[sel]);
2075             } else { // we browse for path
2076                 ASSIGN(fileName, curDir); // kludge: without callback we browse for path
2077             }
2078         }
2079         if(!fileName[0]) return FALSE; // refuse OK when no file
2080         if(!savMode[0]) { // browsing for name only (dialog Browse button)
2081                 snprintf(title, MSG_SIZ, "%s/%s", curDir, fileName);
2082                 SetWidgetText((Option*) savFP, title, TransientDlg);
2083                 currentCps = savCps; // could return to Engine Settings dialog!
2084                 return TRUE;
2085         }
2086         *savFP = fopen(fileName, savMode);
2087         if(*savFP == NULL) return FALSE; // refuse OK if file not openable
2088         ASSIGN(*namePtr, fileName);
2089         ScheduleDelayedEvent(DelayedLoad, 50);
2090         currentCps = savCps; // not sure this is ever non-null
2091         return TRUE;
2092 }
2093
2094 void
2095 FileSelProc (int n, int sel)
2096 {
2097     if(sel<0) return;
2098     ASSIGN(fileName, fileList[sel]);
2099     if(BrowseOK(0)) PopDown(BrowserDlg);
2100 }
2101
2102 int
2103 AlphaNumCompare (char *p, char *q)
2104 {
2105     while(*p) {
2106         if(isdigit(*p) && isdigit(*q) && atoi(p) != atoi(q))
2107              return (atoi(p) > atoi(q) ? 1 : -1);
2108         if(*p != *q) break;
2109         p++, q++;
2110     }
2111     if(*p == *q) return 0;
2112     return (*p > *q ? 1 : -1);
2113 }
2114
2115 int
2116 Comp (const void *s, const void *t)
2117 {
2118     char *p = *(char**) s, *q = *(char**) t;
2119     if(extFlag) {
2120         char *h; int r;
2121         while(h = strchr(p, '.')) p = h+1;
2122         if(p == *(char**) s) p = "";
2123         while(h = strchr(q, '.')) q = h+1;
2124         if(q == *(char**) t) q = "";
2125         r = AlphaNumCompare(p, q);
2126         if(r) return r;
2127     }
2128     return AlphaNumCompare( *(char**) s, *(char**) t );
2129 }
2130
2131 void
2132 ListDir (int pathFlag)
2133 {
2134         DIR *dir;
2135         struct dirent *dp;
2136         struct stat statBuf;
2137         static int lastFlag;
2138         char buf[MSG_SIZ];
2139
2140         if(pathFlag < 0) pathFlag = lastFlag;
2141         lastFlag = pathFlag;
2142         dir = opendir(".");
2143         getcwd(curDir, MSG_SIZ);
2144         snprintf(title, MSG_SIZ, "%s   %s", _("Contents of"), curDir);
2145         folderPtr = filePtr = 0; // clear listing
2146
2147         while (dp = readdir(dir)) { // pass 1: list foders
2148             char *s = dp->d_name, match;
2149             if(!stat(s, &statBuf) && S_ISDIR(statBuf.st_mode)) { // stat succeeds and tells us it is directory
2150                 if(s[0] == '.' && strcmp(s, "..")) continue; // suppress hidden, except ".."
2151                 ASSIGN(folderList[folderPtr], s); folderPtr++;
2152             } else if(!pathFlag) {
2153                 char *s = dp->d_name, match=0;
2154                 if(s[0] == '.') continue; // suppress hidden files
2155                 if(extFilter[0]) { // [HGM] filter on extension
2156                     char *p = extFilter, *q;
2157                     do {
2158                         if(q = strchr(p, ' ')) *q = 0;
2159                         if(strstr(s, p)) match++;
2160                         if(q) *q = ' ';
2161                     } while(q && (p = q+1));
2162                     if(!match) continue;
2163                 }
2164                 ASSIGN(fileList[filePtr], s); filePtr++;
2165             }
2166         }
2167         FREE(folderList[folderPtr]); folderList[folderPtr] = NULL;
2168         FREE(fileList[filePtr]); fileList[filePtr] = NULL;
2169         closedir(dir);
2170         extFlag = 0;         qsort((void*)folderList, folderPtr, sizeof(char*), &Comp);
2171         extFlag = byExtension; qsort((void*)fileList, filePtr, sizeof(char*), &Comp);
2172 }
2173
2174 void
2175 Refresh (int pathFlag)
2176 {
2177     ListDir(pathFlag); // and make new one
2178     LoadListBox(&browseOptions[5], "");
2179     LoadListBox(&browseOptions[6], "");
2180 }
2181
2182 void
2183 Switch (int n)
2184 {
2185     if(byExtension == (n == 4)) return;
2186     extFlag = byExtension = (n == 4);
2187     qsort((void*)fileList, filePtr, sizeof(char*), &Comp);
2188     LoadListBox(&browseOptions[6], "");
2189 }
2190
2191 void
2192 SetTypeFilter (int n)
2193 {
2194     int j = values[n];
2195     if(j == browseOptions[n].value) return; // no change
2196     browseOptions[n].value = j;
2197     SetWidgetLabel(&browseOptions[n], FileTypes[j]);
2198     ASSIGN(extFilter, Extensions[j]);
2199     Refresh(-1); // uses pathflag remembered by ListDir
2200     values[n] = oldVal; // do not disturb combo settings of underlying dialog
2201 }
2202
2203 void
2204 DirSelProc (int n, int sel)
2205 {
2206     if(!chdir(folderList[sel])) { // cd succeeded, so we are in new directory now
2207         Refresh(-1);
2208         SetWidgetLabel(&browseOptions[0], title);
2209     }
2210 }
2211
2212 FILE *
2213 Browse (DialogClass dlg, char *label, char *proposed, char *ext, Boolean pathFlag, char *mode, char **name, FILE **fp)
2214 {
2215     int j=0;
2216     savFP = fp; savMode = mode, namePtr = name, savCps = currentCps, oldVal = values[8]; // save params, for use in callback
2217     ASSIGN(extFilter, ext);
2218     ASSIGN(fileName, proposed ? proposed : "");
2219     for(j=0; Extensions[j]; j++) // look up actual value in list of possible values, to get selection nr
2220         if(extFilter && !strcmp(extFilter, Extensions[j])) break;
2221     if(Extensions[j] == NULL) { j++; ASSIGN(FileTypes[j], extFilter); }
2222     browseOptions[8].value = j;
2223     browseOptions[6].textValue = (char*) (pathFlag ? NULL : &FileSelProc); // disable file listbox during path browsing
2224     ListDir(pathFlag);
2225     currentCps = NULL;
2226     if(GenericPopUp(browseOptions, label, BrowserDlg, dlg, MODAL, 0)) {
2227     }
2228     SetWidgetLabel(&browseOptions[8], FileTypes[j]);
2229 }
2230
2231