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