Fix -topLevel option
[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.onlyOwn, "", NULL, CheckBox, N_("Own Games Only") },
659 { 0, 0, 0, NULL, (void*) &appData.saveGameFile, ".pgn", NULL, FileName,  N_("Save Games on File:") },
660 { 0, 0, 0, NULL, (void*) &appData.savePositionFile, ".fen", NULL, FileName,  N_("Save Final Positions on File:") },
661 { 0, 0, 0, NULL, (void*) &appData.pgnEventHeader, "", NULL, TextBox,  N_("PGN Event Header:") },
662 { 0, 0, 0, NULL, (void*) &appData.oldSaveStyle, "", NULL, CheckBox, N_("Old Save Style (as opposed to PGN)") },
663 { 0, 0, 0, NULL, (void*) &appData.numberTag, "", NULL, CheckBox, N_("Include Number Tag in tourney PGN") },
664 { 0, 0, 0, NULL, (void*) &appData.saveExtendedInfoInPGN, "", NULL, CheckBox, N_("Save Score/Depth Info in PGN") },
665 { 0, 0, 0, NULL, (void*) &appData.saveOutOfBookInfo, "", NULL, CheckBox, N_("Save Out-of-Book Info in PGN           ") },
666 { 0, SAME_ROW, 0, NULL, NULL, "", NULL, EndMark , "" }
667 };
668
669 void
670 SaveOptionsProc ()
671 {
672    GenericPopUp(saveOptions, _("Save Game Options"), TransientDlg, BoardWindow, MODAL, 0);
673 }
674
675 //----------------------------------------------- Sound Options ---------------------------------------------
676
677 static void Test P((int n));
678 static char *trialSound;
679
680 static char *soundNames[] = {
681         N_("No Sound"),
682         N_("Default Beep"),
683         N_("Above WAV File"),
684         N_("Car Horn"),
685         N_("Cymbal"),
686         N_("Ding"),
687         N_("Gong"),
688         N_("Laser"),
689         N_("Penalty"),
690         N_("Phone"),
691         N_("Pop"),
692         N_("Slap"),
693         N_("Wood Thunk"),
694         NULL,
695         N_("User File")
696 };
697
698 static char *soundFiles[] = { // sound files corresponding to above names
699         "",
700         "$",
701         NULL, // kludge alert: as first thing in the dialog readout this is replaced with the user-given .WAV filename
702         "honkhonk.wav",
703         "cymbal.wav",
704         "ding1.wav",
705         "gong.wav",
706         "laser.wav",
707         "penalty.wav",
708         "phone.wav",
709         "pop2.wav",
710         "slap.wav",
711         "woodthunk.wav",
712         NULL,
713         NULL
714 };
715
716 static Option soundOptions[] = {
717 { 0, 0, 0, NULL, (void*) (soundFiles+2) /* kludge! */, ".wav", NULL, FileName, N_("User WAV File:") },
718 { 0, 0, 0, NULL, (void*) &appData.soundProgram, "", NULL, TextBox, N_("Sound Program:") },
719 { 0, 0, 0, NULL, (void*) &trialSound, (char*) soundFiles, soundNames, ComboBox, N_("Try-Out Sound:") },
720 { 0, SAME_ROW, 0, NULL, (void*) &Test, NULL, NULL, Button, N_("Play") },
721 { 0, 0, 0, NULL, (void*) &appData.soundMove, (char*) soundFiles, soundNames, ComboBox, N_("Move:") },
722 { 0, 0, 0, NULL, (void*) &appData.soundIcsWin, (char*) soundFiles, soundNames, ComboBox, N_("Win:") },
723 { 0, 0, 0, NULL, (void*) &appData.soundIcsLoss, (char*) soundFiles, soundNames, ComboBox, N_("Lose:") },
724 { 0, 0, 0, NULL, (void*) &appData.soundIcsDraw, (char*) soundFiles, soundNames, ComboBox, N_("Draw:") },
725 { 0, 0, 0, NULL, (void*) &appData.soundIcsUnfinished, (char*) soundFiles, soundNames, ComboBox, N_("Unfinished:") },
726 { 0, 0, 0, NULL, (void*) &appData.soundIcsAlarm, (char*) soundFiles, soundNames, ComboBox, N_("Alarm:") },
727 { 0, 0, 0, NULL, (void*) &appData.soundChallenge, (char*) soundFiles, soundNames, ComboBox, N_("Challenge:") },
728 { 0, SAME_ROW, 0, NULL, NULL, NULL, NULL, Break, "" },
729 { 0, 0, 0, NULL, (void*) &appData.soundDirectory, "", NULL, PathName, N_("Sounds Directory:") },
730 { 0, 0, 0, NULL, (void*) &appData.soundShout, (char*) soundFiles, soundNames, ComboBox, N_("Shout:") },
731 { 0, 0, 0, NULL, (void*) &appData.soundSShout, (char*) soundFiles, soundNames, ComboBox, N_("S-Shout:") },
732 { 0, 0, 0, NULL, (void*) &appData.soundChannel, (char*) soundFiles, soundNames, ComboBox, N_("Channel:") },
733 { 0, 0, 0, NULL, (void*) &appData.soundChannel1, (char*) soundFiles, soundNames, ComboBox, N_("Channel 1:") },
734 { 0, 0, 0, NULL, (void*) &appData.soundTell, (char*) soundFiles, soundNames, ComboBox, N_("Tell:") },
735 { 0, 0, 0, NULL, (void*) &appData.soundKibitz, (char*) soundFiles, soundNames, ComboBox, N_("Kibitz:") },
736 { 0, 0, 0, NULL, (void*) &appData.soundRequest, (char*) soundFiles, soundNames, ComboBox, N_("Request:") },
737 { 0, 0, 0, NULL, (void*) &appData.soundSeek, (char*) soundFiles, soundNames, ComboBox, N_("Seek:") },
738 { 0, SAME_ROW, 0, NULL, NULL, "", NULL, EndMark , "" }
739 };
740
741 static void
742 Test (int n)
743 {
744     GenericReadout(soundOptions, 1);
745     if(soundFiles[values[2]]) PlaySoundFile(soundFiles[values[2]]);
746 }
747
748 void
749 SoundOptionsProc ()
750 {
751    free(soundFiles[2]);
752    soundFiles[2] = strdup("*");
753    GenericPopUp(soundOptions, _("Sound Options"), TransientDlg, BoardWindow, MODAL, 0);
754 }
755
756 //--------------------------------------------- Board Options --------------------------------------
757
758 static void DefColor P((int n));
759 static void AdjustColor P((int i));
760
761 static char oldPieceDir[MSG_SIZ];
762
763 static int
764 BoardOptionsOK (int n)
765 {
766     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap; else lineGap = defaultLineGap;
767     InitDrawingParams(strcmp(oldPieceDir, appData.pieceDirectory));
768     InitDrawingSizes(-1, 0);
769     DrawPosition(True, NULL);
770     return 1;
771 }
772
773 static Option boardOptions[] = {
774 { 0,          0, 70, NULL, (void*) &appData.whitePieceColor, "", NULL, TextBox, N_("White Piece Color:") },
775 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#FFFFCC", Button, "      " },
776 /* TRANSLATORS: R = single letter for the color red */
777 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
778 /* TRANSLATORS: G = single letter for the color green */
779 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
780 /* TRANSLATORS: B = single letter for the color blue */
781 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
782 /* TRANSLATORS: D = single letter to make a color darker */
783 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
784 { 0,          0, 70, NULL, (void*) &appData.blackPieceColor, "", NULL, TextBox, N_("Black Piece Color:") },
785 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#202020", Button, "      " },
786 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
787 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
788 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
789 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
790 { 0,          0, 70, NULL, (void*) &appData.lightSquareColor, "", NULL, TextBox, N_("Light Square Color:") },
791 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#C8C365", Button, "      " },
792 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
793 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
794 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
795 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
796 { 0,          0, 70, NULL, (void*) &appData.darkSquareColor, "", NULL, TextBox, N_("Dark Square Color:") },
797 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#77A26D", Button, "      " },
798 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
799 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
800 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
801 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
802 { 0,          0, 70, NULL, (void*) &appData.highlightSquareColor, "", NULL, TextBox, N_("Highlight Color:") },
803 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#FFFF00", Button, "      " },
804 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
805 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
806 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
807 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
808 { 0,          0, 70, NULL, (void*) &appData.premoveHighlightColor, "", NULL, TextBox, N_("Premove Highlight Color:") },
809 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#FF0000", Button, "      " },
810 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
811 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
812 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
813 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
814 { 0, 0, 0, NULL, (void*) &appData.upsideDown, "", NULL, CheckBox, N_("Flip Pieces Shogi Style        (Colored buttons restore default)") },
815 //{ 0, 0, 0, NULL, (void*) &appData.allWhite, "", NULL, CheckBox, N_("Use Outline Pieces for Black") },
816 { 0, 0, 0, NULL, (void*) &appData.monoMode, "", NULL, CheckBox, N_("Mono Mode") },
817 { 0,-1, 5, NULL, (void*) &appData.overrideLineGap, "", NULL, Spin, N_("Line Gap ( -1 = default for board size):") },
818 { 0, 0, 0, NULL, (void*) &appData.useBitmaps, "", NULL, CheckBox, N_("Use Board Textures") },
819 { 0, 0, 0, NULL, (void*) &appData.liteBackTextureFile, ".png", NULL, FileName, N_("Light-Squares Texture File:") },
820 { 0, 0, 0, NULL, (void*) &appData.darkBackTextureFile, ".png", NULL, FileName, N_("Dark-Squares Texture File:") },
821 { 0, 0, 0, NULL, (void*) &appData.trueColors, "", NULL, CheckBox, N_("Use external piece bitmaps with their own colors") },
822 { 0, 0, 0, NULL, (void*) &appData.pieceDirectory, "", NULL, PathName, N_("Directory with Pieces Images:") },
823 { 0, 0, 0, NULL, (void*) &BoardOptionsOK, "", NULL, EndMark , "" }
824 };
825
826 static void
827 SetColorText (int n, char *buf)
828 {
829     SetWidgetText(&boardOptions[n-1], buf, TransientDlg);
830     SetColor(buf, &boardOptions[n]);
831 }
832
833 static void
834 DefColor (int n)
835 {
836     SetColorText(n, (char*) boardOptions[n].choice);
837 }
838
839 void
840 RefreshColor (int source, int n)
841 {
842     int col, j, r, g, b, step = 10;
843     char *s, buf[MSG_SIZ]; // color string
844     GetWidgetText(&boardOptions[source], &s);
845     if(sscanf(s, "#%x", &col) != 1) return;   // malformed
846     b = col & 0xFF; g = col & 0xFF00; r = col & 0xFF0000;
847     switch(n) {
848         case 1: r += 0x10000*step;break;
849         case 2: g += 0x100*step;  break;
850         case 3: b += step;        break;
851         case 4: r -= 0x10000*step; g -= 0x100*step; b -= step; break;
852     }
853     if(r < 0) r = 0; if(g < 0) g = 0; if(b < 0) b = 0;
854     if(r > 0xFF0000) r = 0xFF0000; if(g > 0xFF00) g = 0xFF00; if(b > 0xFF) b = 0xFF;
855     col = r | g | b;
856     snprintf(buf, MSG_SIZ, "#%06x", col);
857     for(j=1; j<7; j++) if(buf[j] >= 'a') buf[j] -= 32; // capitalize
858     SetColorText(source+1, buf);
859 }
860
861 static void
862 AdjustColor (int i)
863 {
864     int n = boardOptions[i].value;
865     RefreshColor(i-n-1, n);
866 }
867
868 void
869 BoardOptionsProc ()
870 {
871    strncpy(oldPieceDir, appData.pieceDirectory, MSG_SIZ-1); // to see if it changed
872    GenericPopUp(boardOptions, _("Board Options"), TransientDlg, BoardWindow, MODAL, 0);
873 }
874
875 //-------------------------------------------- ICS Text Menu Options ------------------------------
876
877 Option textOptions[100];
878 static void PutText P((char *text, int pos));
879
880 void
881 SendString (char *p)
882 {
883     char buf[MSG_SIZ], *q;
884     if(q = strstr(p, "$input")) {
885         if(!shellUp[TextMenuDlg]) return;
886         strncpy(buf, p, MSG_SIZ);
887         strncpy(buf + (q-p), q+6, MSG_SIZ-(q-p));
888         PutText(buf, q-p);
889         return;
890     }
891     snprintf(buf, MSG_SIZ, "%s\n", p);
892     SendToICS(buf);
893 }
894
895 void
896 IcsTextProc ()
897 {
898    int i=0, j;
899    char *p, *q, *r;
900    if((p = icsTextMenuString) == NULL) return;
901    do {
902         q = r = p; while(*p && *p != ';') p++;
903         if(textOptions[i].name == NULL) textOptions[i].name = (char*) malloc(MSG_SIZ);
904         for(j=0; j<p-q; j++) textOptions[i].name[j] = *r++;
905         textOptions[i].name[j++] = 0;
906         if(!*p) break;
907         if(*++p == '\n') p++; // optional linefeed after button-text terminating semicolon
908         q = p;
909         textOptions[i].choice = (char**) (r = textOptions[i].name + j);
910         while(*p && (*p != ';' || p[1] != '\n')) textOptions[i].name[j++] = *p++;
911         textOptions[i].name[j++] = 0;
912         if(*p) p += 2;
913         textOptions[i].max = 135;
914         textOptions[i].min = i&1;
915         textOptions[i].handle = NULL;
916         textOptions[i].target = &SendText;
917         textOptions[i].textValue = strstr(r, "$input") ? "#80FF80" : strstr(r, "$name") ? "#FF8080" : "#FFFFFF";
918         textOptions[i].type = Button;
919    } while(++i < 99 && *p);
920    if(i == 0) return;
921    textOptions[i].type = EndMark;
922    textOptions[i].target = NULL;
923    textOptions[i].min = 2;
924    MarkMenu("View.ICStextmenu", TextMenuDlg);
925    GenericPopUp(textOptions, _("ICS text menu"), TextMenuDlg, BoardWindow, NONMODAL, appData.topLevel);
926 }
927
928 //---------------------------------------------------- Edit Comment -----------------------------------
929
930 static char *commentText;
931 static int commentIndex;
932 static void ClearComment P((int n));
933 static void SaveChanges P((int n));
934 int savedIndex;  /* gross that this is global (and even across files...) */
935
936 static int CommentClick P((Option *opt, int n, int x, int y, char *val, int index));
937
938 static int
939 NewComCallback (int n)
940 {
941     ReplaceComment(commentIndex, commentText);
942     return 1;
943 }
944
945 Option commentOptions[] = {
946 { 200, T_VSCRL | T_FILL | T_WRAP | T_TOP, 250, NULL, (void*) &commentText, "", (char **) &CommentClick, TextBox, "" },
947 { 0,     0,     50, NULL, (void*) &ClearComment, NULL, NULL, Button, N_("clear") },
948 { 0, SAME_ROW, 100, NULL, (void*) &SaveChanges, NULL, NULL, Button, N_("save changes") },
949 { 0, SAME_ROW,  0,  NULL, (void*) &NewComCallback, "", NULL, EndMark , "" }
950 };
951
952 static int
953 CommentClick (Option *opt, int n, int x, int y, char *val, int index)
954 {
955         if(n != 3) return FALSE; // only button-3 press is of interest
956         ReplaceComment(savedIndex, val);
957         if(savedIndex != currentMove) ToNrEvent(savedIndex);
958         LoadVariation( index, val ); // [HGM] also does the actual moving to it, now
959         return TRUE;
960 }
961
962 static void
963 SaveChanges (int n)
964 {
965     GenericReadout(commentOptions, 0);
966     ReplaceComment(commentIndex, commentText);
967 }
968
969 static void
970 ClearComment (int n)
971 {
972     SetWidgetText(&commentOptions[0], "", CommentDlg);
973 }
974
975 void
976 NewCommentPopup (char *title, char *text, int index)
977 {
978     if(DialogExists(CommentDlg)) { // if already exists, alter title and content
979         SetDialogTitle(CommentDlg, title);
980         SetWidgetText(&commentOptions[0], text, CommentDlg);
981     }
982     if(commentText) free(commentText); commentText = strdup(text);
983     commentIndex = index;
984     MarkMenu("View.Comments", CommentDlg);
985     if(GenericPopUp(commentOptions, title, CommentDlg, BoardWindow, NONMODAL, appData.topLevel))
986         AddHandler(&commentOptions[0], CommentDlg, 1);
987 }
988
989 void
990 EditCommentPopUp (int index, char *title, char *text)
991 {
992     savedIndex = index;
993     if (text == NULL) text = "";
994     NewCommentPopup(title, text, index);
995 }
996
997 void
998 CommentPopUp (char *title, char *text)
999 {
1000     savedIndex = currentMove; // [HGM] vari
1001     NewCommentPopup(title, text, currentMove);
1002 }
1003
1004 void
1005 CommentPopDown ()
1006 {
1007     PopDown(CommentDlg);
1008 }
1009
1010
1011 void
1012 EditCommentProc ()
1013 {
1014     if (PopDown(CommentDlg)) { // popdown succesful
1015 //      MarkMenuItem("Edit.EditComment", False);
1016 //      MarkMenuItem("View.Comments", False);
1017     } else // was not up
1018         EditCommentEvent();
1019 }
1020
1021 //------------------------------------------------------ Edit Tags ----------------------------------
1022
1023 static void changeTags P((int n));
1024 static char *tagsText;
1025
1026 static int
1027 NewTagsCallback (int n)
1028 {
1029     ReplaceTags(tagsText, &gameInfo);
1030     return 1;
1031 }
1032
1033 static Option tagsOptions[] = {
1034 {   0,   0,   0, NULL, NULL, NULL, NULL, Label,  NULL },
1035 { 200, T_VSCRL | T_FILL | T_WRAP | T_TOP, 200, NULL, (void*) &tagsText, "", NULL, TextBox, "" },
1036 {   0,   0, 100, NULL, (void*) &changeTags, NULL, NULL, Button, N_("save changes") },
1037 { 0,SAME_ROW, 0, NULL, (void*) &NewTagsCallback, "", NULL, EndMark , "" }
1038 };
1039
1040 static void
1041 changeTags (int n)
1042 {
1043     GenericReadout(tagsOptions, 1);
1044     if(bookUp) SaveToBook(tagsText); else
1045     ReplaceTags(tagsText, &gameInfo);
1046 }
1047
1048 void
1049 NewTagsPopup (char *text, char *msg)
1050 {
1051     char *title = bookUp ? _("Edit book") : _("Tags");
1052
1053     if(DialogExists(TagsDlg)) { // if already exists, alter title and content
1054         SetWidgetText(&tagsOptions[1], text, TagsDlg);
1055         SetDialogTitle(TagsDlg, title);
1056     }
1057     if(tagsText) free(tagsText); tagsText = strdup(text);
1058     tagsOptions[0].name = msg;
1059     MarkMenu("View.Tags", TagsDlg);
1060     GenericPopUp(tagsOptions, title, TagsDlg, BoardWindow, NONMODAL, appData.topLevel);
1061 }
1062
1063 void
1064 TagsPopUp (char *tags, char *msg)
1065 {
1066     NewTagsPopup(tags, cmailMsgLoaded ? msg : NULL);
1067 }
1068
1069 void
1070 EditTagsPopUp (char *tags, char **dest)
1071 {   // wrapper to preserve old name used in back-end
1072     NewTagsPopup(tags, NULL);
1073 }
1074
1075 void
1076 TagsPopDown()
1077 {
1078     PopDown(TagsDlg);
1079     bookUp = False;
1080 }
1081
1082 void
1083 EditTagsProc ()
1084 {
1085   if (bookUp || !PopDown(TagsDlg)) EditTagsEvent();
1086 }
1087
1088 //---------------------------------------------- ICS Input Box ----------------------------------
1089
1090 char *icsText;
1091
1092 // [HGM] code borrowed from winboard.c (which should thus go to backend.c!)
1093 #define HISTORY_SIZE 64
1094 static char *history[HISTORY_SIZE];
1095 static int histIn = 0, histP = 0;
1096
1097 static void
1098 SaveInHistory (char *cmd)
1099 {
1100   if (history[histIn] != NULL) {
1101     free(history[histIn]);
1102     history[histIn] = NULL;
1103   }
1104   if (*cmd == NULLCHAR) return;
1105   history[histIn] = StrSave(cmd);
1106   histIn = (histIn + 1) % HISTORY_SIZE;
1107   if (history[histIn] != NULL) {
1108     free(history[histIn]);
1109     history[histIn] = NULL;
1110   }
1111   histP = histIn;
1112 }
1113
1114 static char *
1115 PrevInHistory (char *cmd)
1116 {
1117   int newhp;
1118   if (histP == histIn) {
1119     if (history[histIn] != NULL) free(history[histIn]);
1120     history[histIn] = StrSave(cmd);
1121   }
1122   newhp = (histP - 1 + HISTORY_SIZE) % HISTORY_SIZE;
1123   if (newhp == histIn || history[newhp] == NULL) return NULL;
1124   histP = newhp;
1125   return history[histP];
1126 }
1127
1128 static char *
1129 NextInHistory ()
1130 {
1131   if (histP == histIn) return NULL;
1132   histP = (histP + 1) % HISTORY_SIZE;
1133   return history[histP];
1134 }
1135 // end of borrowed code
1136
1137 Option boxOptions[] = {
1138 {  30, T_TOP, 400, NULL, (void*) &icsText, "", NULL, TextBox, "" },
1139 {  0,  NO_OK,   0, NULL, NULL, "", NULL, EndMark , "" }
1140 };
1141
1142 void
1143 ICSInputSendText ()
1144 {
1145     char *val;
1146
1147     GetWidgetText(&boxOptions[0], &val);
1148     SaveInHistory(val);
1149     SendMultiLineToICS(val);
1150     SetWidgetText(&boxOptions[0], "", InputBoxDlg);
1151 }
1152
1153 void
1154 IcsKey (int n)
1155 {   // [HGM] input: let up-arrow recall previous line from history
1156     char *val = NULL; // to suppress spurious warning
1157
1158     if (!shellUp[InputBoxDlg]) return;
1159     switch(n) {
1160       case 0:
1161         ICSInputSendText();
1162         return;
1163       case 1:
1164         GetWidgetText(&boxOptions[0], &val);
1165         val = PrevInHistory(val);
1166         break;
1167       case -1:
1168         val = NextInHistory();
1169     }
1170     SetWidgetText(&boxOptions[0], val ? val : "", InputBoxDlg);
1171 }
1172
1173 static void
1174 PutText (char *text, int pos)
1175 {
1176     char buf[MSG_SIZ], *p;
1177
1178     if(strstr(text, "$add ") == text) {
1179         GetWidgetText(&boxOptions[0], &p);
1180         snprintf(buf, MSG_SIZ, "%s%s", p, text+5); text = buf;
1181         pos += strlen(p) - 5;
1182     }
1183     SetWidgetText(&boxOptions[0], text, TextMenuDlg);
1184     SetInsertPos(&boxOptions[0], pos);
1185     HardSetFocus(&boxOptions[0]);
1186 }
1187
1188 void
1189 ICSInputBoxPopUp ()
1190 {
1191     MarkMenu("View.ICSInputBox", InputBoxDlg);
1192     if(GenericPopUp(boxOptions, _("ICS input box"), InputBoxDlg, BoardWindow, NONMODAL, 0))
1193         AddHandler(&boxOptions[0], InputBoxDlg, 3);
1194     CursorAtEnd(&boxOptions[0]);
1195 }
1196
1197 void
1198 IcsInputBoxProc ()
1199 {
1200     if (!PopDown(InputBoxDlg)) ICSInputBoxPopUp();
1201 }
1202
1203 //--------------------------------------------- Move Type In ------------------------------------------
1204
1205 static int TypeInOK P((int n));
1206
1207 Option typeOptions[] = {
1208 { 30, T_TOP, 400, NULL, (void*) &icsText, "", NULL, TextBox, "" },
1209 { 0,  NO_OK,   0, NULL, (void*) &TypeInOK, "", NULL, EndMark , "" }
1210 };
1211
1212 static int
1213 TypeInOK (int n)
1214 {
1215     TypeInDoneEvent(icsText);
1216     return TRUE;
1217 }
1218
1219 void
1220 PopUpMoveDialog (char firstchar)
1221 {
1222     static char buf[2];
1223     buf[0] = firstchar; ASSIGN(icsText, buf);
1224     if(GenericPopUp(typeOptions, _("Type a move"), TransientDlg, BoardWindow, MODAL, 0))
1225         AddHandler(&typeOptions[0], TransientDlg, 2);
1226     CursorAtEnd(&typeOptions[0]);
1227 }
1228
1229 void
1230 BoxAutoPopUp (char *buf)
1231 {
1232         if(appData.icsActive) { // text typed to board in ICS mode: divert to ICS input box
1233             if(!appData.autoBox) return;
1234             if(DialogExists(InputBoxDlg)) { // box already exists: append to current contents
1235                 char *p, newText[MSG_SIZ];
1236                 GetWidgetText(&boxOptions[0], &p);
1237                 snprintf(newText, MSG_SIZ, "%s%c", p, *buf);
1238                 SetWidgetText(&boxOptions[0], newText, InputBoxDlg);
1239                 if(shellUp[InputBoxDlg]) HardSetFocus (&boxOptions[0]); //why???
1240             } else icsText = buf; // box did not exist: make sure it pops up with char in it
1241             ICSInputBoxPopUp();
1242         } else PopUpMoveDialog(*buf);
1243 }
1244
1245 //------------------------------------------ Engine Settings ------------------------------------
1246
1247 void
1248 SettingsPopUp (ChessProgramState *cps)
1249 {
1250    if(!cps->nrOptions) { DisplayNote(_("Engine has no options")); return; }
1251    currentCps = cps;
1252    GenericPopUp(cps->option, _("Engine Settings"), TransientDlg, BoardWindow, MODAL, 0);
1253 }
1254
1255 void
1256 FirstSettingsProc ()
1257 {
1258     SettingsPopUp(&first);
1259 }
1260
1261 void
1262 SecondSettingsProc ()
1263 {
1264    if(WaitForEngine(&second, SettingsMenuIfReady)) return;
1265    SettingsPopUp(&second);
1266 }
1267
1268 //----------------------------------------------- Load Engine --------------------------------------
1269
1270 char *engineDir, *engineLine, *nickName, *params;
1271 Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick, secondEng;
1272
1273 static void EngSel P((int n, int sel));
1274 static int InstallOK P((int n));
1275
1276 static Option installOptions[] = {
1277 {   0,LR|T2T, 0, NULL, NULL, NULL, NULL, Label, N_("Select engine from list:") },
1278 { 300,LR|TB,200, NULL, (void*) engineMnemonic, (char*) &EngSel, NULL, ListBox, "" },
1279 { 0,SAME_ROW, 0, NULL, NULL, NULL, NULL, Break, NULL },
1280 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("or specify one below:") },
1281 {   0,  0,    0, NULL, (void*) &nickName, NULL, NULL, TextBox, N_("Nickname (optional):") },
1282 {   0,  0,    0, NULL, (void*) &useNick, NULL, NULL, CheckBox, N_("Use nickname in PGN player tags of engine-engine games") },
1283 {   0,  0,    0, NULL, (void*) &engineDir, NULL, NULL, PathName, N_("Engine Directory:") },
1284 {   0,  0,    0, NULL, (void*) &engineName, NULL, NULL, FileName, N_("Engine Command:") },
1285 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("(Directory will be derived from engine path when empty)") },
1286 {   0,  0,    0, NULL, (void*) &isUCI, NULL, NULL, CheckBox, N_("UCI") },
1287 {   0,  0,    0, NULL, (void*) &v1, NULL, NULL, CheckBox, N_("WB protocol v1 (do not wait for engine features)") },
1288 {   0,  0,    0, NULL, (void*) &hasBook, NULL, NULL, CheckBox, N_("Must not use GUI book") },
1289 {   0,  0,    0, NULL, (void*) &addToList, NULL, NULL, CheckBox, N_("Add this engine to the list") },
1290 {   0,  0,    0, NULL, (void*) &storeVariant, NULL, NULL, CheckBox, N_("Force current variant with this engine") },
1291 {   0,  0,    0, NULL, (void*) &InstallOK, "", NULL, EndMark , "" }
1292 };
1293
1294 static int
1295 InstallOK (int n)
1296 {
1297     if(n && (n = SelectedListBoxItem(&installOptions[1])) > 0) { // called by pressing OK, and engine selected
1298         ASSIGN(engineLine, engineList[n]);
1299     }
1300     PopDown(TransientDlg); // early popdown, to allow FreezeUI to instate grab
1301     if(!secondEng) Load(&first, 0); else Load(&second, 1);
1302     return FALSE; // no double PopDown!
1303 }
1304
1305 static void
1306 EngSel (int n, int sel)
1307 {
1308     int nr;
1309     char buf[MSG_SIZ];
1310     if(sel < 1) buf[0] = NULLCHAR; // back to top level
1311     else if(engineList[sel][0] == '#') safeStrCpy(buf, engineList[sel], MSG_SIZ); // group header, open group
1312     else { // normal line, select engine
1313         ASSIGN(engineLine, engineList[sel]);
1314         InstallOK(0);
1315         return;
1316     }
1317     nr = NamesToList(firstChessProgramNames, engineList, engineMnemonic, buf); // replace list by only the group contents
1318     ASSIGN(engineMnemonic[0], buf);
1319     LoadListBox(&installOptions[1], _("# no engines are installed"), -1, -1);
1320     HighlightWithScroll(&installOptions[1], 0, nr);
1321 }
1322
1323 static void
1324 LoadEngineProc (int engineNr, char *title)
1325 {
1326    isUCI = storeVariant = v1 = useNick = False; addToList = hasBook = True; // defaults
1327    secondEng = engineNr;
1328    if(engineLine)   free(engineLine);   engineLine = strdup("");
1329    if(engineDir)    free(engineDir);    engineDir = strdup(".");
1330    if(nickName)     free(nickName);     nickName = strdup("");
1331    if(params)       free(params);       params = strdup("");
1332    ASSIGN(engineMnemonic[0], "");
1333    NamesToList(firstChessProgramNames, engineList, engineMnemonic, "");
1334    GenericPopUp(installOptions, title, TransientDlg, BoardWindow, MODAL, 0);
1335 }
1336
1337 void
1338 LoadEngine1Proc ()
1339 {
1340     LoadEngineProc (0, _("Load first engine"));
1341 }
1342
1343 void
1344 LoadEngine2Proc ()
1345 {
1346     LoadEngineProc (1, _("Load second engine"));
1347 }
1348
1349 //----------------------------------------------------- Edit Book -----------------------------------------
1350
1351 void
1352 EditBookProc ()
1353 {
1354     EditBookEvent();
1355 }
1356
1357 //--------------------------------------------------- New Shuffle Game ------------------------------
1358
1359 static void SetRandom P((int n));
1360
1361 static int
1362 ShuffleOK (int n)
1363 {
1364     ResetGameEvent();
1365     return 1;
1366 }
1367
1368 static Option shuffleOptions[] = {
1369   {   0,  0,   50, NULL, (void*) &shuffleOpenings, NULL, NULL, CheckBox, N_("shuffle") },
1370   { 0,-1,2000000000, NULL, (void*) &appData.defaultFrcPosition, "", NULL, Spin, N_("Start-position number:") },
1371   {   0,  0,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("randomize") },
1372   {   0,  SAME_ROW,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("pick fixed") },
1373   { 0,SAME_ROW, 0, NULL, (void*) &ShuffleOK, "", NULL, EndMark , "" }
1374 };
1375
1376 static void
1377 SetRandom (int n)
1378 {
1379     int r = n==2 ? -1 : random() & (1<<30)-1;
1380     char buf[MSG_SIZ];
1381     snprintf(buf, MSG_SIZ,  "%d", r);
1382     SetWidgetText(&shuffleOptions[1], buf, TransientDlg);
1383     SetWidgetState(&shuffleOptions[0], True);
1384 }
1385
1386 void
1387 ShuffleMenuProc ()
1388 {
1389     GenericPopUp(shuffleOptions, _("New Shuffle Game"), TransientDlg, BoardWindow, MODAL, 0);
1390 }
1391
1392 //------------------------------------------------------ Time Control -----------------------------------
1393
1394 static int TcOK P((int n));
1395 int tmpMoves, tmpTc, tmpInc, tmpOdds1, tmpOdds2, tcType;
1396
1397 static void SetTcType P((int n));
1398
1399 static char *
1400 Value (int n)
1401 {
1402         static char buf[MSG_SIZ];
1403         snprintf(buf, MSG_SIZ, "%d", n);
1404         return buf;
1405 }
1406
1407 static Option tcOptions[] = {
1408 {   0,  0,    0, NULL, (void*) &SetTcType, NULL, NULL, Button, N_("classical") },
1409 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("incremental") },
1410 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("fixed max") },
1411 {   0,  0,  200, NULL, (void*) &tmpMoves, NULL, NULL, Spin, N_("Moves per session:") },
1412 {   0,  0,10000, NULL, (void*) &tmpTc,    NULL, NULL, Spin, N_("Initial time (min):") },
1413 {   0, 0, 10000, NULL, (void*) &tmpInc,   NULL, NULL, Spin, N_("Increment or max (sec/move):") },
1414 {   0,  0,    0, NULL, NULL, NULL, NULL, Label, N_("Time-Odds factors:") },
1415 {   0,  1, 1000, NULL, (void*) &tmpOdds1, NULL, NULL, Spin, N_("Engine #1") },
1416 {   0,  1, 1000, NULL, (void*) &tmpOdds2, NULL, NULL, Spin, N_("Engine #2 / Human") },
1417 {   0,  0,    0, NULL, (void*) &TcOK, "", NULL, EndMark , "" }
1418 };
1419
1420 static int
1421 TcOK (int n)
1422 {
1423     char *tc;
1424     if(tcType == 0 && tmpMoves <= 0) return 0;
1425     if(tcType == 2 && tmpInc <= 0) return 0;
1426     GetWidgetText(&tcOptions[4], &tc); // get original text, in case it is min:sec
1427     searchTime = 0;
1428     switch(tcType) {
1429       case 0:
1430         if(!ParseTimeControl(tc, -1, tmpMoves)) return 0;
1431         appData.movesPerSession = tmpMoves;
1432         ASSIGN(appData.timeControl, tc);
1433         appData.timeIncrement = -1;
1434         break;
1435       case 1:
1436         if(!ParseTimeControl(tc, tmpInc, 0)) return 0;
1437         ASSIGN(appData.timeControl, tc);
1438         appData.timeIncrement = tmpInc;
1439         break;
1440       case 2:
1441         searchTime = tmpInc;
1442     }
1443     appData.firstTimeOdds = first.timeOdds = tmpOdds1;
1444     appData.secondTimeOdds = second.timeOdds = tmpOdds2;
1445     Reset(True, True);
1446     return 1;
1447 }
1448
1449 static void
1450 SetTcType (int n)
1451 {
1452     switch(tcType = n) {
1453       case 0:
1454         SetWidgetText(&tcOptions[3], Value(tmpMoves), TransientDlg);
1455         SetWidgetText(&tcOptions[4], Value(tmpTc), TransientDlg);
1456         SetWidgetText(&tcOptions[5], _("Unused"), TransientDlg);
1457         break;
1458       case 1:
1459         SetWidgetText(&tcOptions[3], _("Unused"), TransientDlg);
1460         SetWidgetText(&tcOptions[4], Value(tmpTc), TransientDlg);
1461         SetWidgetText(&tcOptions[5], Value(tmpInc), TransientDlg);
1462         break;
1463       case 2:
1464         SetWidgetText(&tcOptions[3], _("Unused"), TransientDlg);
1465         SetWidgetText(&tcOptions[4], _("Unused"), TransientDlg);
1466         SetWidgetText(&tcOptions[5], Value(tmpInc), TransientDlg);
1467     }
1468 }
1469
1470 void
1471 TimeControlProc ()
1472 {
1473    tmpMoves = appData.movesPerSession;
1474    tmpInc = appData.timeIncrement; if(tmpInc < 0) tmpInc = 0;
1475    tmpOdds1 = tmpOdds2 = 1; tcType = 0;
1476    tmpTc = atoi(appData.timeControl);
1477    GenericPopUp(tcOptions, _("Time Control"), TransientDlg, BoardWindow, MODAL, 0);
1478    SetTcType(searchTime ? 2 : appData.timeIncrement < 0 ? 0 : 1);
1479 }
1480
1481 //------------------------------- Ask Question -----------------------------------------
1482
1483 int SendReply P((int n));
1484 char pendingReplyPrefix[MSG_SIZ];
1485 ProcRef pendingReplyPR;
1486 char *answer;
1487
1488 Option askOptions[] = {
1489 { 0, 0, 0, NULL, NULL, NULL, NULL, Label,  NULL },
1490 { 0, 0, 0, NULL, (void*) &answer, "", NULL, TextBox, "" },
1491 { 0, 0, 0, NULL, (void*) &SendReply, "", NULL, EndMark , "" }
1492 };
1493
1494 int
1495 SendReply (int n)
1496 {
1497     char buf[MSG_SIZ];
1498     int err;
1499     char *reply=answer;
1500 //    GetWidgetText(&askOptions[1], &reply);
1501     safeStrCpy(buf, pendingReplyPrefix, sizeof(buf)/sizeof(buf[0]) );
1502     if (*buf) strncat(buf, " ", MSG_SIZ - strlen(buf) - 1);
1503     strncat(buf, reply, MSG_SIZ - strlen(buf) - 1);
1504     strncat(buf, "\n",  MSG_SIZ - strlen(buf) - 1);
1505     OutputToProcess(pendingReplyPR, buf, strlen(buf), &err); // does not go into debug file??? => bug
1506     if (err) DisplayFatalError(_("Error writing to chess program"), err, 0);
1507     return TRUE;
1508 }
1509
1510 void
1511 AskQuestion (char *title, char *question, char *replyPrefix, ProcRef pr)
1512 {
1513     safeStrCpy(pendingReplyPrefix, replyPrefix, sizeof(pendingReplyPrefix)/sizeof(pendingReplyPrefix[0]) );
1514     pendingReplyPR = pr;
1515     ASSIGN(answer, "");
1516     askOptions[0].name = question;
1517     if(GenericPopUp(askOptions, title, AskDlg, BoardWindow, MODAL, 0))
1518         AddHandler(&askOptions[1], AskDlg, 2);
1519 }
1520
1521 //---------------------------- Promotion Popup --------------------------------------
1522
1523 static int count;
1524
1525 static void PromoPick P((int n));
1526
1527 static Option promoOptions[] = {
1528 {   0,         0,    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,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1536 {   0, SAME_ROW | NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
1537 };
1538
1539 static void
1540 PromoPick (int n)
1541 {
1542     int promoChar = promoOptions[n+count].value;
1543
1544     PopDown(PromoDlg);
1545
1546     if (promoChar == 0) fromX = -1;
1547     if (fromX == -1) return;
1548
1549     if (! promoChar) {
1550         fromX = fromY = -1;
1551         ClearHighlights();
1552         return;
1553     }
1554     UserMoveEvent(fromX, fromY, toX, toY, promoChar);
1555
1556     if (!appData.highlightLastMove || gotPremove) ClearHighlights();
1557     if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
1558     fromX = fromY = -1;
1559 }
1560
1561 static void
1562 SetPromo (char *name, int nr, char promoChar)
1563 {
1564     ASSIGN(promoOptions[nr].name, name);
1565     promoOptions[nr].value = promoChar;
1566     promoOptions[nr].min = SAME_ROW;
1567 }
1568
1569 void
1570 PromotionPopUp ()
1571 { // choice depends on variant: prepare dialog acordingly
1572   count = 8;
1573   SetPromo(_("Cancel"), --count, 0); // Beware: GenericPopUp cannot handle user buttons named "cancel" (lowe case)!
1574   if(gameInfo.variant != VariantShogi) {
1575     if (!appData.testLegality || gameInfo.variant == VariantSuicide ||
1576         gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove) ||
1577         gameInfo.variant == VariantGiveaway) {
1578       SetPromo(_("King"), --count, 'k');
1579     }
1580     if(gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove)) {
1581       SetPromo(_("Captain"), --count, 'c');
1582       SetPromo(_("Lieutenant"), --count, 'l');
1583       SetPromo(_("General"), --count, 'g');
1584       SetPromo(_("Warlord"), --count, 'w');
1585     } else {
1586       SetPromo(_("Knight"), --count, 'n');
1587       SetPromo(_("Bishop"), --count, 'b');
1588       SetPromo(_("Rook"), --count, 'r');
1589       if(gameInfo.variant == VariantCapablanca ||
1590          gameInfo.variant == VariantGothic ||
1591          gameInfo.variant == VariantCapaRandom) {
1592         SetPromo(_("Archbishop"), --count, 'a');
1593         SetPromo(_("Chancellor"), --count, 'c');
1594       }
1595       SetPromo(_("Queen"), --count, 'q');
1596     }
1597   } else // [HGM] shogi
1598   {
1599       SetPromo(_("Defer"), --count, '=');
1600       SetPromo(_("Promote"), --count, '+');
1601   }
1602   promoOptions[count].min = 0;
1603   GenericPopUp(promoOptions + count, "Promotion", PromoDlg, BoardWindow, NONMODAL, 0);
1604 }
1605
1606 //---------------------------- Chat Windows ----------------------------------------------
1607
1608 static char *line, *memo, *partner, *texts[MAX_CHAT], dirty[MAX_CHAT];
1609 static int activePartner;
1610
1611 void ChatSwitch P((int n));
1612 int  ChatOK P((int n));
1613
1614 Option chatOptions[] = {
1615 { 0,   T_TOP,    100, NULL, (void*) &partner, NULL, NULL, TextBox, N_("Chat partner:") },
1616 { 1, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1617 { 2, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1618 { 3, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1619 { 4, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1620 { 100, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &memo, NULL, NULL, TextBox, "" },
1621 {  0,    0,  510, NULL, (void*) &line, NULL, NULL, TextBox, "" },
1622 { 0, NO_OK|SAME_ROW, 0, NULL, (void*) &ChatOK, NULL, NULL, EndMark , "" }
1623 };
1624
1625 void
1626 OutputChatMessage (int partner, char *mess)
1627 {
1628     char *p = texts[partner];
1629     int len = strlen(mess) + 1;
1630
1631     if(p) len += strlen(p);
1632     texts[partner] = (char*) malloc(len);
1633     snprintf(texts[partner], len, "%s%s", p ? p : "", mess);
1634     FREE(p);
1635     if(partner == activePartner) {
1636         AppendText(&chatOptions[5], mess);
1637         SetInsertPos(&chatOptions[5], len-2);
1638     } else {
1639         SetColor("#FFC000", &chatOptions[partner + (partner < activePartner)]);
1640         dirty[partner] = 1;
1641     }
1642 }
1643
1644 int
1645 ChatOK (int n)
1646 {   // can only be called through <Enter> in chat-partner text-edit, as there is no OK button
1647     char buf[MSG_SIZ];
1648
1649     if(!partner || strcmp(partner, chatPartner[activePartner])) {
1650         safeStrCpy(chatPartner[activePartner], partner, MSG_SIZ);
1651         SetWidgetText(&chatOptions[5], "", -1); // clear text if we alter partner
1652         SetWidgetText(&chatOptions[6], "", ChatDlg); // clear text if we alter partner
1653         HardSetFocus(&chatOptions[6]);
1654     }
1655     if(line[0]) { // something was typed
1656         SetWidgetText(&chatOptions[6], "", ChatDlg);
1657         // from here on it could be back-end
1658         if(line[strlen(line)-1] == '\n') line[strlen(line)-1] = NULLCHAR;
1659         SaveInHistory(line);
1660         if(!strcmp("whispers", chatPartner[activePartner]))
1661               snprintf(buf, MSG_SIZ, "whisper %s\n", line); // WHISPER box uses "whisper" to send
1662         else if(!strcmp("shouts", chatPartner[activePartner]))
1663               snprintf(buf, MSG_SIZ, "shout %s\n", line); // SHOUT box uses "shout" to send
1664         else {
1665             if(!atoi(chatPartner[activePartner])) {
1666                 snprintf(buf, MSG_SIZ, "> %s\n", line); // echo only tells to handle, not channel
1667                 OutputChatMessage(activePartner, buf);
1668                 snprintf(buf, MSG_SIZ, "xtell %s %s\n", chatPartner[activePartner], line);
1669             } else
1670                 snprintf(buf, MSG_SIZ, "tell %s %s\n", chatPartner[activePartner], line);
1671         }
1672         SendToICS(buf);
1673     }
1674     return FALSE; // never pop down
1675 }
1676
1677 void
1678 ChatSwitch (int n)
1679 {
1680     int i, j;
1681     if(n <= activePartner) n--;
1682     activePartner = n;
1683     if(!texts[n]) texts[n] = strdup("");
1684     dirty[n] = 0;
1685     SetWidgetText(&chatOptions[5], texts[n], ChatDlg);
1686     SetInsertPos(&chatOptions[5], strlen(texts[n]));
1687     SetWidgetText(&chatOptions[0], chatPartner[n], ChatDlg);
1688     for(i=j=0; i<MAX_CHAT; i++) {
1689         if(i == activePartner) continue;
1690         SetWidgetLabel(&chatOptions[++j], chatPartner[i]);
1691         SetColor(dirty[i] ? "#FFC000" : "#FFFFFF", &chatOptions[j]);
1692     }
1693     SetWidgetText(&chatOptions[6], "", ChatDlg);
1694     HardSetFocus(&chatOptions[6]);
1695 }
1696
1697 void
1698 ChatProc ()
1699 {
1700     if(GenericPopUp(chatOptions, _("Chat box"), ChatDlg, BoardWindow, NONMODAL, 0))
1701         AddHandler(&chatOptions[0], ChatDlg, 2), AddHandler(&chatOptions[6], ChatDlg, 2); // treats return as OK
1702     MarkMenu("View.OpenChatWindow", ChatDlg);
1703 }
1704
1705 //--------------------------------- Game-List options dialog ------------------------------------------
1706
1707 char *strings[LPUSERGLT_SIZE];
1708 int stringPtr;
1709
1710 void
1711 GLT_ClearList ()
1712 {
1713     strings[0] = NULL;
1714     stringPtr = 0;
1715 }
1716
1717 void
1718 GLT_AddToList (char *name)
1719 {
1720     strings[stringPtr++] = name;
1721     strings[stringPtr] = NULL;
1722 }
1723
1724 Boolean
1725 GLT_GetFromList (int index, char *name)
1726 {
1727   safeStrCpy(name, strings[index], MSG_SIZ);
1728   return TRUE;
1729 }
1730
1731 void
1732 GLT_DeSelectList ()
1733 {
1734 }
1735
1736 static void GLT_Button P((int n));
1737 static int GLT_OK P((int n));
1738
1739 static Option listOptions[] = {
1740 {300, LR|TB, 200, NULL, (void*) strings, "", NULL, ListBox, "" }, // For GTK we need to specify a height, as default would just show 3 lines
1741 { 0,    0,     0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("factory") },
1742 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("up") },
1743 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("down") },
1744 { 0, SAME_ROW, 0, NULL, (void*) &GLT_OK, "", NULL, EndMark , "" }
1745 };
1746
1747 static int
1748 GLT_OK (int n)
1749 {
1750     GLT_ParseList();
1751     appData.gameListTags = strdup(lpUserGLT);
1752     return 1;
1753 }
1754
1755 static void
1756 GLT_Button (int n)
1757 {
1758     int index = SelectedListBoxItem (&listOptions[0]);
1759     char *p;
1760     if (index < 0) {
1761         DisplayError(_("No tag selected"), 0);
1762         return;
1763     }
1764     p = strings[index];
1765     if (n == 3) {
1766         if(index >= strlen(GLT_ALL_TAGS)) return;
1767         strings[index] = strings[index+1];
1768         strings[++index] = p;
1769         LoadListBox(&listOptions[0], "?", index, index-1); // only change the two specified entries
1770     } else
1771     if (n == 2) {
1772         if(index == 0) return;
1773         strings[index] = strings[index-1];
1774         strings[--index] = p;
1775         LoadListBox(&listOptions[0], "?", index, index+1);
1776     } else
1777     if (n == 1) {
1778       safeStrCpy(lpUserGLT, GLT_DEFAULT_TAGS, LPUSERGLT_SIZE);
1779       GLT_TagsToList(lpUserGLT);
1780       index = 0;
1781       LoadListBox(&listOptions[0], "?", -1, -1);
1782     }
1783     HighlightListBoxItem(&listOptions[0], index);
1784 }
1785
1786 void
1787 GameListOptionsPopUp (DialogClass parent)
1788 {
1789     safeStrCpy(lpUserGLT, appData.gameListTags, LPUSERGLT_SIZE);
1790     GLT_TagsToList(lpUserGLT);
1791
1792     GenericPopUp(listOptions, _("Game-list options"), TransientDlg, parent, MODAL, 0);
1793 }
1794
1795 void
1796 GameListOptionsProc ()
1797 {
1798     GameListOptionsPopUp(BoardWindow);
1799 }
1800
1801 //----------------------------- Error popup in various uses -----------------------------
1802
1803 /*
1804  * [HGM] Note:
1805  * XBoard has always had some pathologic behavior with multiple simultaneous error popups,
1806  * (which can occur even for modal popups when asynchrounous events, e.g. caused by engine, request a popup),
1807  * and this new implementation reproduces that as well:
1808  * Only the shell of the last instance is remembered in shells[ErrorDlg] (which replaces errorShell),
1809  * so that PopDowns ordered from the code always refer to that instance, and once that is down,
1810  * have no clue as to how to reach the others. For the Delete Window button calling PopDown this
1811  * has now been repaired, as the action routine assigned to it gets the shell passed as argument.
1812  */
1813
1814 int errorUp = False;
1815
1816 void
1817 ErrorPopDown ()
1818 {
1819     if (!errorUp) return;
1820     dialogError = errorUp = False;
1821     PopDown(ErrorDlg); PopDown(FatalDlg); // on explicit request we pop down any error dialog
1822     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
1823 }
1824
1825 static int
1826 ErrorOK (int n)
1827 {
1828     dialogError = errorUp = False;
1829     PopDown(n == 1 ? FatalDlg : ErrorDlg); // kludge: non-modal dialogs have one less (dummy) option
1830     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
1831     return FALSE; // prevent second Popdown !
1832 }
1833
1834 static Option errorOptions[] = {
1835 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // dummy option: will never be displayed
1836 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // textValue field will be set before popup
1837 { 0,NO_CANCEL,0, NULL, (void*) &ErrorOK, "", NULL, EndMark , "" }
1838 };
1839
1840 void
1841 ErrorPopUp (char *title, char *label, int modal)
1842 {
1843     errorUp = True;
1844     errorOptions[1].name = label;
1845     if(dialogError = shellUp[TransientDlg])
1846         GenericPopUp(errorOptions+1, title, FatalDlg, TransientDlg, MODAL, 0); // pop up as daughter of the transient dialog
1847     else
1848         GenericPopUp(errorOptions+modal, title, modal ? FatalDlg: ErrorDlg, BoardWindow, modal, 0); // kludge: option start address indicates modality
1849 }
1850
1851 void
1852 DisplayError (String message, int error)
1853 {
1854     char buf[MSG_SIZ];
1855
1856     if (error == 0) {
1857         if (appData.debugMode || appData.matchMode) {
1858             fprintf(stderr, "%s: %s\n", programName, message);
1859         }
1860     } else {
1861         if (appData.debugMode || appData.matchMode) {
1862             fprintf(stderr, "%s: %s: %s\n",
1863                     programName, message, strerror(error));
1864         }
1865         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
1866         message = buf;
1867     }
1868     ErrorPopUp(_("Error"), message, FALSE);
1869 }
1870
1871
1872 void
1873 DisplayMoveError (String message)
1874 {
1875     fromX = fromY = -1;
1876     ClearHighlights();
1877     DrawPosition(FALSE, NULL);
1878     if (appData.debugMode || appData.matchMode) {
1879         fprintf(stderr, "%s: %s\n", programName, message);
1880     }
1881     if (appData.popupMoveErrors) {
1882         ErrorPopUp(_("Error"), message, FALSE);
1883     } else {
1884         DisplayMessage(message, "");
1885     }
1886 }
1887
1888
1889 void
1890 DisplayFatalError (String message, int error, int status)
1891 {
1892     char buf[MSG_SIZ];
1893
1894     errorExitStatus = status;
1895     if (error == 0) {
1896         fprintf(stderr, "%s: %s\n", programName, message);
1897     } else {
1898         fprintf(stderr, "%s: %s: %s\n",
1899                 programName, message, strerror(error));
1900         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
1901         message = buf;
1902     }
1903     if(mainOptions[W_BOARD].handle) {
1904         if (appData.popupExitMessage) {
1905             ErrorPopUp(status ? _("Fatal Error") : _("Exiting"), message, TRUE);
1906         } else {
1907             ExitEvent(status);
1908         }
1909     }
1910 }
1911
1912 void
1913 DisplayInformation (String message)
1914 {
1915     ErrorPopDown();
1916     ErrorPopUp(_("Information"), message, TRUE);
1917 }
1918
1919 void
1920 DisplayNote (String message)
1921 {
1922     ErrorPopDown();
1923     ErrorPopUp(_("Note"), message, FALSE);
1924 }
1925
1926 void
1927 DisplayTitle (char *text)
1928 {
1929     char title[MSG_SIZ];
1930     char icon[MSG_SIZ];
1931
1932     if (text == NULL) text = "";
1933
1934     if(partnerUp) { SetDialogTitle(DummyDlg, text); return; }
1935
1936     if (*text != NULLCHAR) {
1937       safeStrCpy(icon, text, sizeof(icon)/sizeof(icon[0]) );
1938       safeStrCpy(title, text, sizeof(title)/sizeof(title[0]) );
1939     } else if (appData.icsActive) {
1940         snprintf(icon, sizeof(icon), "%s", appData.icsHost);
1941         snprintf(title, sizeof(title), "%s: %s", programName, appData.icsHost);
1942     } else if (appData.cmailGameName[0] != NULLCHAR) {
1943         snprintf(icon, sizeof(icon), "%s", "CMail");
1944         snprintf(title,sizeof(title), "%s: %s", programName, "CMail");
1945 #ifdef GOTHIC
1946     // [HGM] license: This stuff should really be done in back-end, but WinBoard already had a pop-up for it
1947     } else if (gameInfo.variant == VariantGothic) {
1948       safeStrCpy(icon,  programName, sizeof(icon)/sizeof(icon[0]) );
1949       safeStrCpy(title, GOTHIC,     sizeof(title)/sizeof(title[0]) );
1950 #endif
1951 #ifdef FALCON
1952     } else if (gameInfo.variant == VariantFalcon) {
1953       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
1954       safeStrCpy(title, FALCON, sizeof(title)/sizeof(title[0]) );
1955 #endif
1956     } else if (appData.noChessProgram) {
1957       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
1958       safeStrCpy(title, programName, sizeof(title)/sizeof(title[0]) );
1959     } else {
1960       safeStrCpy(icon, first.tidy, sizeof(icon)/sizeof(icon[0]) );
1961         snprintf(title,sizeof(title), "%s: %s", programName, first.tidy);
1962     }
1963     SetWindowTitle(text, title, icon);
1964 }
1965
1966 #define PAUSE_BUTTON "P"
1967 #define PIECE_MENU_SIZE 18
1968 static String pieceMenuStrings[2][PIECE_MENU_SIZE+1] = {
1969     { N_("White"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
1970       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
1971       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
1972       N_("Empty square"), N_("Clear board"), NULL },
1973     { N_("Black"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
1974       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
1975       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
1976       N_("Empty square"), N_("Clear board"), NULL }
1977 };
1978 /* must be in same order as pieceMenuStrings! */
1979 static ChessSquare pieceMenuTranslation[2][PIECE_MENU_SIZE] = {
1980     { WhitePlay, (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
1981         WhiteRook, WhiteQueen, WhiteKing, (ChessSquare) 0, WhiteAlfil,
1982         WhiteCannon, WhiteAngel, WhiteMarshall, (ChessSquare) 0,
1983         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
1984     { BlackPlay, (ChessSquare) 0, BlackPawn, BlackKnight, BlackBishop,
1985         BlackRook, BlackQueen, BlackKing, (ChessSquare) 0, BlackAlfil,
1986         BlackCannon, BlackAngel, BlackMarshall, (ChessSquare) 0,
1987         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
1988 };
1989
1990 #define DROP_MENU_SIZE 6
1991 static String dropMenuStrings[DROP_MENU_SIZE+1] = {
1992     "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"), N_("Queen"), NULL
1993   };
1994 /* must be in same order as dropMenuStrings! */
1995 static ChessSquare dropMenuTranslation[DROP_MENU_SIZE] = {
1996     (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
1997     WhiteRook, WhiteQueen
1998 };
1999
2000 // [HGM] experimental code to pop up window just like the main window, using GenercicPopUp
2001
2002 static Option *Exp P((int n, int x, int y));
2003 void MenuCallback P((int n));
2004 void SizeKludge P((int n));
2005 static Option *LogoW P((int n, int x, int y));
2006 static Option *LogoB P((int n, int x, int y));
2007
2008 static int pmFromX = -1, pmFromY = -1;
2009 void *userLogo;
2010
2011 void
2012 DisplayLogos (Option *w1, Option *w2)
2013 {
2014         void *whiteLogo = first.programLogo, *blackLogo = second.programLogo;
2015         if(appData.autoLogo) {
2016
2017           switch(gameMode) { // pick logos based on game mode
2018             case IcsObserving:
2019                 whiteLogo = second.programLogo; // ICS logo
2020                 blackLogo = second.programLogo;
2021             default:
2022                 break;
2023             case IcsPlayingWhite:
2024                 if(!appData.zippyPlay) whiteLogo = userLogo;
2025                 blackLogo = second.programLogo; // ICS logo
2026                 break;
2027             case IcsPlayingBlack:
2028                 whiteLogo = second.programLogo; // ICS logo
2029                 blackLogo = appData.zippyPlay ? first.programLogo : userLogo;
2030                 break;
2031             case TwoMachinesPlay:
2032                 if(first.twoMachinesColor[0] == 'b') {
2033                     whiteLogo = second.programLogo;
2034                     blackLogo = first.programLogo;
2035                 }
2036                 break;
2037             case MachinePlaysWhite:
2038                 blackLogo = userLogo;
2039                 break;
2040             case MachinePlaysBlack:
2041                 whiteLogo = userLogo;
2042                 blackLogo = first.programLogo;
2043           }
2044         }
2045         DrawLogo(w1, whiteLogo);
2046         DrawLogo(w2, blackLogo);
2047 }
2048
2049 static void
2050 PMSelect (int n)
2051 {   // user callback for board context menus
2052     if (pmFromX < 0 || pmFromY < 0) return;
2053     if(n == W_DROP) DropMenuEvent(dropMenuTranslation[values[n]], pmFromX, pmFromY);
2054     else EditPositionMenuEvent(pieceMenuTranslation[n - W_MENUW][values[n]], pmFromX, pmFromY);
2055 }
2056
2057 static void
2058 CCB (int n)
2059 {
2060     shiftKey = (ShiftKeys() & 3) != 0;
2061     if(n < 0) { // button != 1
2062         n = -n;
2063         if(shiftKey && (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack)) {
2064             AdjustClock(n == W_BLACK, 1);
2065         }
2066     } else
2067     ClockClick(n == W_BLACK);
2068 }
2069
2070 Option mainOptions[] = { // description of main window in terms of generic dialog creator
2071 { 0, 0xCA, 0, NULL, NULL, "", NULL, BarBegin, "" }, // menu bar
2072   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("File") },
2073   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Edit") },
2074   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("View") },
2075   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Mode") },
2076   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Action") },
2077   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Engine") },
2078   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Options") },
2079   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Help") },
2080 { 0, 0, 0, NULL, (void*)&SizeKludge, "", NULL, BarEnd, "" },
2081 { 0, LR|T2T|BORDER|SAME_ROW, 0, NULL, NULL, "", NULL, Label, "1" }, // optional title in window
2082 { 50,    LL|TT,            100, NULL, (void*) &LogoW, NULL, NULL, -1, "" }, // white logo
2083 { 12,   L2L|T2T,           200, NULL, (void*) &CCB, NULL, NULL, Label, "White" }, // white clock
2084 { 13,   R2R|T2T|SAME_ROW,  200, NULL, (void*) &CCB, NULL, NULL, Label, "Black" }, // black clock
2085 { 50,    RR|TT|SAME_ROW,   100, NULL, (void*) &LogoB, NULL, NULL, -1, "" }, // black logo
2086 { 0, LR|T2T|BORDER,        401, NULL, NULL, "", NULL, -1, "2" }, // backup for title in window (if no room for other)
2087 { 0, LR|T2T|BORDER,        270, NULL, NULL, "", NULL, Label, "message" }, // message field
2088 { 0, RR|TT|SAME_ROW,       125, NULL, NULL, "", NULL, BoxBegin, "" }, // (optional) button bar
2089   { 0,    0,     0, NULL, (void*) &ToStartEvent, NULL, NULL, Button, N_("<<") },
2090   { 0, SAME_ROW, 0, NULL, (void*) &BackwardEvent, NULL, NULL, Button, N_("<") },
2091   { 0, SAME_ROW, 0, NULL, (void*) &PauseEvent, NULL, NULL, Button, N_(PAUSE_BUTTON) },
2092   { 0, SAME_ROW, 0, NULL, (void*) &ForwardEvent, NULL, NULL, Button, N_(">") },
2093   { 0, SAME_ROW, 0, NULL, (void*) &ToEndEvent, NULL, NULL, Button, N_(">>") },
2094 { 0, 0, 0, NULL, NULL, "", NULL, BoxEnd, "" },
2095 { 401, LR|TB, 401, NULL, (char*) &Exp, NULL, NULL, Graph, "shadow board" }, // board
2096   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[0], PopUp, "menuW" },
2097   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[1], PopUp, "menuB" },
2098   { -1, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, dropMenuStrings, PopUp, "menuD" },
2099 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2100 };
2101
2102 Option *
2103 LogoW (int n, int x, int y)
2104 {
2105     if(n == 10) DisplayLogos(&mainOptions[W_WHITE-1], NULL);
2106     return NULL;
2107 }
2108
2109 Option *
2110 LogoB (int n, int x, int y)
2111 {
2112     if(n == 10) DisplayLogos(NULL, &mainOptions[W_BLACK+1]);
2113     return NULL;
2114 }
2115
2116 void
2117 SizeKludge (int n)
2118 {   // callback called by GenericPopUp immediately after sizing the menu bar
2119     int width = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2120     int w = width - 44 - mainOptions[n].min;
2121     mainOptions[W_TITLE].max = w; // width left behind menu bar
2122     if(w < 0.4*width) // if no reasonable amount of space for title, force small layout
2123         mainOptions[W_SMALL].type = mainOptions[W_TITLE].type, mainOptions[W_TITLE].type = -1;
2124 }
2125
2126 void
2127 MenuCallback (int n)
2128 {
2129     MenuProc *proc = (MenuProc *) (((MenuItem*)(mainOptions[n].choice))[values[n]].proc);
2130
2131     if(!proc) RecentEngineEvent(values[n] - firstEngineItem); else (proc)();
2132 }
2133
2134 static Option *
2135 Exp (int n, int x, int y)
2136 {
2137     static int but1, but3, oldW, oldH;
2138     int menuNr = -3, sizing;
2139
2140     if(n == 0) { // motion
2141         if(SeekGraphClick(Press, x, y, 1)) return NULL;
2142         if(but1 && !PromoScroll(x, y)) DragPieceMove(x, y);
2143         if(but3) MovePV(x, y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
2144         return NULL;
2145     }
2146     if(n != 10 && PopDown(PromoDlg)) fromX = fromY = -1; // user starts fiddling with board when promotion dialog is up
2147     shiftKey = ShiftKeys();
2148     controlKey = (shiftKey & 0xC) != 0;
2149     shiftKey = (shiftKey & 3) != 0;
2150     switch(n) {
2151         case  1: LeftClick(Press,   x, y), but1 = 1; break;
2152         case -1: LeftClick(Release, x, y), but1 = 0; break;
2153         case  2: shiftKey = !shiftKey;
2154         case  3: menuNr = RightClick(Press,   x, y, &pmFromX, &pmFromY), but3 = 1; break;
2155         case -2: shiftKey = !shiftKey;
2156         case -3: menuNr = RightClick(Release, x, y, &pmFromX, &pmFromY), but3 = 0; break;
2157         case 10:
2158             sizing = (oldW != x || oldH != y);
2159             oldW = x; oldH = y;
2160             InitDrawingHandle(mainOptions + W_BOARD);
2161             if(sizing) return NULL; // don't redraw while sizing
2162             DrawPosition(True, NULL);
2163         default:
2164             return NULL;
2165     }
2166
2167     switch(menuNr) {
2168       case 0: return &mainOptions[shiftKey ? W_MENUW: W_MENUB];
2169       case 1: SetupDropMenu(); return &mainOptions[W_DROP];
2170       case 2:
2171       case -1: ErrorPopDown();
2172       case -2:
2173       default: break; // -3, so no clicks caught
2174     }
2175     return NULL;
2176 }
2177
2178 Option *
2179 BoardPopUp (int squareSize, int lineGap, void *clockFontThingy)
2180 {
2181     int i, size = BOARD_WIDTH*(squareSize + lineGap) + lineGap, logo = appData.logoSize;
2182     mainOptions[W_WHITE].choice = (char**) clockFontThingy;
2183     mainOptions[W_BLACK].choice = (char**) clockFontThingy;
2184     mainOptions[W_BOARD].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2185     mainOptions[W_BOARD].max = mainOptions[W_SMALL].max = size; // board size
2186     mainOptions[W_SMALL].max = size - 2; // board title (subtract border!)
2187     mainOptions[W_BLACK].max = mainOptions[W_WHITE].max = size/2-3; // clock width
2188     mainOptions[W_MESSG].max = appData.showButtonBar ? size-135 : size-2; // message
2189     mainOptions[W_MENU].max = size-40; // menu bar
2190     mainOptions[W_TITLE].type = appData.titleInWindow ? Label : -1 ;
2191     if(logo && logo <= size/4) { // Activate logos
2192         mainOptions[W_WHITE-1].type = mainOptions[W_BLACK+1].type = Graph;
2193         mainOptions[W_WHITE-1].max  = mainOptions[W_BLACK+1].max  = logo;
2194         mainOptions[W_WHITE-1].value= mainOptions[W_BLACK+1].value= logo/2;
2195         mainOptions[W_WHITE].min  |= SAME_ROW;
2196         mainOptions[W_WHITE].max  = mainOptions[W_BLACK].max  -= logo + 4;
2197         mainOptions[W_WHITE].name = mainOptions[W_BLACK].name = "Double\nHeight";
2198     }
2199     if(!appData.showButtonBar) for(i=W_BUTTON; i<W_BOARD; i++) mainOptions[i].type = -1;
2200     for(i=0; i<8; i++) mainOptions[i+1].choice = (char**) menuBar[i].mi;
2201     AppendEnginesToMenu(appData.recentEngineList);
2202     GenericPopUp(mainOptions, "XBoard", BoardWindow, BoardWindow, NONMODAL, 1); // allways top-level
2203     return mainOptions;
2204 }
2205
2206 static Option *
2207 SlaveExp (int n, int x, int y)
2208 {
2209     if(n == 10) { // expose event
2210         flipView = !flipView; partnerUp = !partnerUp;
2211         DrawPosition(True, NULL); // [HGM] dual: draw other board in other orientation
2212         flipView = !flipView; partnerUp = !partnerUp;
2213     }
2214     return NULL;
2215 }
2216
2217 Option dualOptions[] = { // auxiliary board window
2218 { 0, L2L|T2T,              198, NULL, NULL, NULL, NULL, Label, "White" }, // white clock
2219 { 0, R2R|T2T|SAME_ROW,     198, NULL, NULL, NULL, NULL, Label, "Black" }, // black clock
2220 { 0, LR|T2T|BORDER,        401, NULL, NULL, NULL, NULL, Label, "This feature is experimental" }, // message field
2221 { 401, LR|TT, 401, NULL, (char*) &SlaveExp, NULL, NULL, Graph, "shadow board" }, // board
2222 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2223 };
2224
2225 void
2226 SlavePopUp ()
2227 {
2228     int size = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2229     // copy params from main board
2230     dualOptions[0].choice = mainOptions[W_WHITE].choice;
2231     dualOptions[1].choice = mainOptions[W_BLACK].choice;
2232     dualOptions[3].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2233     dualOptions[3].max = dualOptions[2].max = size; // board width
2234     dualOptions[0].max = dualOptions[1].max = size/2 - 3; // clock width
2235     GenericPopUp(dualOptions, "XBoard", DummyDlg, BoardWindow, NONMODAL, appData.topLevel);
2236     SlaveResize(dualOptions+3);
2237 }
2238
2239 void
2240 DisplayWhiteClock (long timeRemaining, int highlight)
2241 {
2242     if(appData.noGUI) return;
2243     if(twoBoards && partnerUp) {
2244         DisplayTimerLabel(&dualOptions[0], _("White"), timeRemaining, highlight);
2245         return;
2246     }
2247     DisplayTimerLabel(&mainOptions[W_WHITE], _("White"), timeRemaining, highlight);
2248     if(highlight) SetClockIcon(0);
2249 }
2250
2251 void
2252 DisplayBlackClock (long timeRemaining, int highlight)
2253 {
2254     if(appData.noGUI) return;
2255     if(twoBoards && partnerUp) {
2256         DisplayTimerLabel(&dualOptions[1], _("Black"), timeRemaining, highlight);
2257         return;
2258     }
2259     DisplayTimerLabel(&mainOptions[W_BLACK], _("Black"), timeRemaining, highlight);
2260     if(highlight) SetClockIcon(1);
2261 }
2262
2263
2264 //---------------------------------------------
2265
2266 void
2267 DisplayMessage (char *message, char *extMessage)
2268 {
2269   /* display a message in the message widget */
2270
2271   char buf[MSG_SIZ];
2272
2273   if (extMessage)
2274     {
2275       if (*message)
2276         {
2277           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
2278           message = buf;
2279         }
2280       else
2281         {
2282           message = extMessage;
2283         };
2284     };
2285
2286     safeStrCpy(lastMsg, message, MSG_SIZ); // [HGM] make available
2287
2288   /* need to test if messageWidget already exists, since this function
2289      can also be called during the startup, if for example a Xresource
2290      is not set up correctly */
2291   if(mainOptions[W_MESSG].handle)
2292     SetWidgetLabel(&mainOptions[W_MESSG], message);
2293
2294   return;
2295 }
2296
2297 //----------------------------------- File Browser -------------------------------
2298
2299 #ifdef HAVE_DIRENT_H
2300 #include <dirent.h>
2301 #else
2302 #include <sys/dir.h>
2303 #define dirent direct
2304 #endif
2305
2306 #include <sys/stat.h>
2307
2308 #define MAXFILES 1000
2309
2310 static ChessProgramState *savCps;
2311 static FILE **savFP;
2312 static char *fileName, *extFilter, *savMode, **namePtr;
2313 static int folderPtr, filePtr, oldVal, byExtension, extFlag, pageStart, cnt;
2314 static char curDir[MSG_SIZ], title[MSG_SIZ], *folderList[MAXFILES], *fileList[MAXFILES];
2315
2316 static char *FileTypes[] = {
2317 "Chess Games",
2318 "Chess Positions",
2319 "Tournaments",
2320 "Opening Books",
2321 "Sound files",
2322 "Images",
2323 "Settings (*.ini)",
2324 "Log files",
2325 "All files",
2326 NULL,
2327 "PGN",
2328 "Old-Style Games",
2329 "FEN",
2330 "Old-Style Positions",
2331 NULL,
2332 NULL
2333 };
2334
2335 static char *Extensions[] = {
2336 ".pgn .game",
2337 ".fen .epd .pos",
2338 ".trn",
2339 ".bin",
2340 ".wav",
2341 ".xpm",
2342 ".ini",
2343 ".log",
2344 "",
2345 "INVALID",
2346 ".pgn",
2347 ".game",
2348 ".fen",
2349 ".pos",
2350 NULL,
2351 ""
2352 };
2353
2354 void DirSelProc P((int n, int sel));
2355 void FileSelProc P((int n, int sel));
2356 void SetTypeFilter P((int n));
2357 int BrowseOK P((int n));
2358 void Switch P((int n));
2359 void CreateDir P((int n));
2360
2361 Option browseOptions[] = {
2362 {   0,    LR|T2T,      500, NULL, NULL, NULL, NULL, Label, title },
2363 {   0,    L2L|T2T,     250, NULL, NULL, NULL, NULL, Label, N_("Directories:") },
2364 {   0,R2R|T2T|SAME_ROW,100, NULL, NULL, NULL, NULL, Label, N_("Files:") },
2365 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by name") },
2366 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by type") },
2367 { 300,    L2L|TB,      250, NULL, (void*) folderList, (char*) &DirSelProc, NULL, ListBox, "" },
2368 { 300, R2R|TB|SAME_ROW,250, NULL, (void*) fileList, (char*) &FileSelProc, NULL, ListBox, "" },
2369 {   0,       0,        300, NULL, (void*) &fileName, NULL, NULL, TextBox, N_("Filename:") },
2370 {   0,    SAME_ROW,    120, NULL, (void*) &CreateDir, NULL, NULL, Button, N_("New directory") },
2371 {   0, COMBO_CALLBACK, 150, NULL, (void*) &SetTypeFilter, NULL, FileTypes, ComboBox, N_("File type:") },
2372 {   0,    SAME_ROW,      0, NULL, (void*) &BrowseOK, "", NULL, EndMark , "" }
2373 };
2374
2375 int
2376 BrowseOK (int n)
2377 {
2378         if(!fileName[0]) { // it is enough to have a file selected
2379             if(browseOptions[6].textValue) { // kludge: if callback specified we browse for file
2380                 int sel = SelectedListBoxItem(&browseOptions[6]);
2381                 if(sel < 0 || sel >= filePtr) return FALSE;
2382                 ASSIGN(fileName, fileList[sel]);
2383             } else { // we browse for path
2384                 ASSIGN(fileName, curDir); // kludge: without callback we browse for path
2385             }
2386         }
2387         if(!fileName[0]) return FALSE; // refuse OK when no file
2388         if(!savMode[0]) { // browsing for name only (dialog Browse button)
2389                 if(fileName[0] == '/') // We already had a path name
2390                     snprintf(title, MSG_SIZ, "%s", fileName);
2391                 else
2392                     snprintf(title, MSG_SIZ, "%s/%s", curDir, fileName);
2393                 SetWidgetText((Option*) savFP, title, TransientDlg);
2394                 currentCps = savCps; // could return to Engine Settings dialog!
2395                 return TRUE;
2396         }
2397         *savFP = fopen(fileName, savMode);
2398         if(*savFP == NULL) return FALSE; // refuse OK if file not openable
2399         ASSIGN(*namePtr, fileName);
2400         ScheduleDelayedEvent(DelayedLoad, 50);
2401         currentCps = savCps; // not sure this is ever non-null
2402         return TRUE;
2403 }
2404
2405 int
2406 AlphaNumCompare (char *p, char *q)
2407 {
2408     while(*p) {
2409         if(isdigit(*p) && isdigit(*q) && atoi(p) != atoi(q))
2410              return (atoi(p) > atoi(q) ? 1 : -1);
2411         if(*p != *q) break;
2412         p++, q++;
2413     }
2414     if(*p == *q) return 0;
2415     return (*p > *q ? 1 : -1);
2416 }
2417
2418 int
2419 Comp (const void *s, const void *t)
2420 {
2421     char *p = *(char**) s, *q = *(char**) t;
2422     if(extFlag) {
2423         char *h; int r;
2424         while(h = strchr(p, '.')) p = h+1;
2425         if(p == *(char**) s) p = "";
2426         while(h = strchr(q, '.')) q = h+1;
2427         if(q == *(char**) t) q = "";
2428         r = AlphaNumCompare(p, q);
2429         if(r) return r;
2430     }
2431     return AlphaNumCompare( *(char**) s, *(char**) t );
2432 }
2433
2434 void
2435 ListDir (int pathFlag)
2436 {
2437         DIR *dir;
2438         struct dirent *dp;
2439         struct stat statBuf;
2440         static int lastFlag;
2441
2442         if(pathFlag < 0) pathFlag = lastFlag;
2443         lastFlag = pathFlag;
2444         dir = opendir(".");
2445         getcwd(curDir, MSG_SIZ);
2446         snprintf(title, MSG_SIZ, "%s   %s", _("Contents of"), curDir);
2447         folderPtr = filePtr = cnt = 0; // clear listing
2448
2449         while (dp = readdir(dir)) { // pass 1: list foders
2450             char *s = dp->d_name;
2451             if(!stat(s, &statBuf) && S_ISDIR(statBuf.st_mode)) { // stat succeeds and tells us it is directory
2452                 if(s[0] == '.' && strcmp(s, "..")) continue; // suppress hidden, except ".."
2453                 ASSIGN(folderList[folderPtr], s); if(folderPtr < MAXFILES-2) folderPtr++;
2454             } else if(!pathFlag) {
2455                 char *s = dp->d_name, match=0;
2456 //              if(cnt == pageStart) { ASSIGN }
2457                 if(s[0] == '.') continue; // suppress hidden files
2458                 if(extFilter[0]) { // [HGM] filter on extension
2459                     char *p = extFilter, *q;
2460                     do {
2461                         if(q = strchr(p, ' ')) *q = 0;
2462                         if(strstr(s, p)) match++;
2463                         if(q) *q = ' ';
2464                     } while(q && (p = q+1));
2465                     if(!match) continue;
2466                 }
2467                 if(filePtr == MAXFILES-2) continue;
2468                 if(cnt++ < pageStart) continue;
2469                 ASSIGN(fileList[filePtr], s); filePtr++;
2470             }
2471         }
2472         if(filePtr == MAXFILES-2) { ASSIGN(fileList[filePtr], _("\177 next page")); filePtr++; }
2473         FREE(folderList[folderPtr]); folderList[folderPtr] = NULL;
2474         FREE(fileList[filePtr]); fileList[filePtr] = NULL;
2475         closedir(dir);
2476         extFlag = 0;         qsort((void*)folderList, folderPtr, sizeof(char*), &Comp);
2477         extFlag = byExtension; qsort((void*)fileList, filePtr, sizeof(char*), &Comp);
2478 }
2479
2480 void
2481 Refresh (int pathFlag)
2482 {
2483     ListDir(pathFlag); // and make new one
2484     LoadListBox(&browseOptions[5], "", -1, -1);
2485     LoadListBox(&browseOptions[6], "", -1, -1);
2486     SetWidgetLabel(&browseOptions[0], title);
2487 }
2488
2489 void
2490 CreateDir (int n)
2491 {
2492     char *name, *errmsg = "";
2493     GetWidgetText(&browseOptions[n-1], &name);
2494     if(!name[0]) errmsg = _("FIRST TYPE DIRECTORY NAME HERE"); else
2495     if(mkdir(name, 0755)) errmsg = _("TRY ANOTHER NAME");
2496     else {
2497         chdir(name);
2498         Refresh(-1);
2499     }
2500     SetWidgetText(&browseOptions[n-1], errmsg, BrowserDlg);
2501 }
2502
2503 void
2504 Switch (int n)
2505 {
2506     if(byExtension == (n == 4)) return;
2507     extFlag = byExtension = (n == 4);
2508     qsort((void*)fileList, filePtr, sizeof(char*), &Comp);
2509     LoadListBox(&browseOptions[6], "", -1, -1);
2510 }
2511
2512 void
2513 SetTypeFilter (int n)
2514 {
2515     int j = values[n];
2516     if(j == browseOptions[n].value) return; // no change
2517     browseOptions[n].value = j;
2518     SetWidgetLabel(&browseOptions[n], FileTypes[j]);
2519     ASSIGN(extFilter, Extensions[j]);
2520     pageStart = 0;
2521     Refresh(-1); // uses pathflag remembered by ListDir
2522     values[n] = oldVal; // do not disturb combo settings of underlying dialog
2523 }
2524
2525 void
2526 FileSelProc (int n, int sel)
2527 {
2528     if(sel<0) return;
2529     if(sel == MAXFILES-2) { pageStart = cnt; Refresh(-1); return; }
2530     ASSIGN(fileName, fileList[sel]);
2531     if(BrowseOK(0)) PopDown(BrowserDlg);
2532 }
2533
2534 void
2535 DirSelProc (int n, int sel)
2536 {
2537     if(!chdir(folderList[sel])) { // cd succeeded, so we are in new directory now
2538         Refresh(-1);
2539     }
2540 }
2541
2542 void
2543 Browse (DialogClass dlg, char *label, char *proposed, char *ext, Boolean pathFlag, char *mode, char **name, FILE **fp)
2544 {
2545     int j=0;
2546     savFP = fp; savMode = mode, namePtr = name, savCps = currentCps, oldVal = values[9]; // save params, for use in callback
2547     ASSIGN(extFilter, ext);
2548     ASSIGN(fileName, proposed ? proposed : "");
2549     for(j=0; Extensions[j]; j++) // look up actual value in list of possible values, to get selection nr
2550         if(extFilter && !strcmp(extFilter, Extensions[j])) break;
2551     if(Extensions[j] == NULL) { j++; ASSIGN(FileTypes[j], extFilter); }
2552     browseOptions[9].value = j;
2553     browseOptions[6].textValue = (char*) (pathFlag ? NULL : &FileSelProc); // disable file listbox during path browsing
2554     pageStart = 0; ListDir(pathFlag);
2555     currentCps = NULL;
2556     GenericPopUp(browseOptions, label, BrowserDlg, dlg, MODAL, 0);
2557     SetWidgetLabel(&browseOptions[9], FileTypes[j]);
2558 }
2559
2560 static char *openName;
2561 FileProc fileProc;
2562 char *fileOpenMode;
2563 FILE *openFP;
2564
2565 void
2566 DelayedLoad ()
2567 {
2568   (void) (*fileProc)(openFP, 0, openName);
2569 }
2570
2571 void
2572 FileNamePopUp (char *label, char *def, char *filter, FileProc proc, char *openMode)
2573 {
2574     fileProc = proc;            /* I can't see a way not */
2575     fileOpenMode = openMode;    /*   to use globals here */
2576     FileNamePopUpWrapper(label, def, filter, proc, False, openMode, &openName, &openFP);
2577 }
2578
2579 void
2580 ActivateTheme (int col)
2581 {
2582 }
2583
2584 char *
2585 Col2Text (int n)
2586 {
2587     return NULL;
2588 }