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