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