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