Updated copyright notice to 2014
[xboard.git] / dialogs.c
1 /*
2  * dialogs.c -- platform-independent code for dialogs of XBoard
3  *
4  * Copyright 2000, 2009, 2010, 2011, 2012, 2013, 2014 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 { -1,                   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         PopDown(TransientDlg);
481         Reset(True, True);
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 ? val : "", InputBoxDlg);
1171     SetInsertPos(&boxOptions[0], strlen(val));
1172 }
1173
1174 static void
1175 PutText (char *text, int pos)
1176 {
1177     char buf[MSG_SIZ], *p;
1178
1179     if(strstr(text, "$add ") == text) {
1180         GetWidgetText(&boxOptions[0], &p);
1181         snprintf(buf, MSG_SIZ, "%s%s", p, text+5); text = buf;
1182         pos += strlen(p) - 5;
1183     }
1184     SetWidgetText(&boxOptions[0], text, TextMenuDlg);
1185     SetInsertPos(&boxOptions[0], pos);
1186     HardSetFocus(&boxOptions[0]);
1187 }
1188
1189 void
1190 ICSInputBoxPopUp ()
1191 {
1192     MarkMenu("View.ICSInputBox", InputBoxDlg);
1193     if(GenericPopUp(boxOptions, _("ICS input box"), InputBoxDlg, BoardWindow, NONMODAL, 0))
1194         AddHandler(&boxOptions[0], InputBoxDlg, 3);
1195     CursorAtEnd(&boxOptions[0]);
1196 }
1197
1198 void
1199 IcsInputBoxProc ()
1200 {
1201     if (!PopDown(InputBoxDlg)) ICSInputBoxPopUp();
1202 }
1203
1204 //--------------------------------------------- Move Type In ------------------------------------------
1205
1206 static int TypeInOK P((int n));
1207
1208 Option typeOptions[] = {
1209 { 30, T_TOP, 400, NULL, (void*) &icsText, "", NULL, TextBox, "" },
1210 { 0,  NO_OK,   0, NULL, (void*) &TypeInOK, "", NULL, EndMark , "" }
1211 };
1212
1213 static int
1214 TypeInOK (int n)
1215 {
1216     TypeInDoneEvent(icsText);
1217     return TRUE;
1218 }
1219
1220 void
1221 PopUpMoveDialog (char firstchar)
1222 {
1223     static char buf[2];
1224     buf[0] = firstchar; ASSIGN(icsText, buf);
1225     if(GenericPopUp(typeOptions, _("Type a move"), TransientDlg, BoardWindow, MODAL, 0))
1226         AddHandler(&typeOptions[0], TransientDlg, 2);
1227     CursorAtEnd(&typeOptions[0]);
1228 }
1229
1230 void
1231 BoxAutoPopUp (char *buf)
1232 {
1233         if(!appData.autoBox) return;
1234         if(appData.icsActive) { // text typed to board in ICS mode: divert to ICS input box
1235             if(DialogExists(InputBoxDlg)) { // box already exists: append to current contents
1236                 char *p, newText[MSG_SIZ];
1237                 GetWidgetText(&boxOptions[0], &p);
1238                 snprintf(newText, MSG_SIZ, "%s%c", p, *buf);
1239                 SetWidgetText(&boxOptions[0], newText, InputBoxDlg);
1240                 if(shellUp[InputBoxDlg]) HardSetFocus (&boxOptions[0]); //why???
1241             } else icsText = buf; // box did not exist: make sure it pops up with char in it
1242             ICSInputBoxPopUp();
1243         } else PopUpMoveDialog(*buf);
1244 }
1245
1246 //------------------------------------------ Engine Settings ------------------------------------
1247
1248 void
1249 SettingsPopUp (ChessProgramState *cps)
1250 {
1251    if(!cps->nrOptions) { DisplayNote(_("Engine has no options")); return; }
1252    currentCps = cps;
1253    GenericPopUp(cps->option, _("Engine Settings"), TransientDlg, BoardWindow, MODAL, 0);
1254 }
1255
1256 void
1257 FirstSettingsProc ()
1258 {
1259     SettingsPopUp(&first);
1260 }
1261
1262 void
1263 SecondSettingsProc ()
1264 {
1265    if(WaitForEngine(&second, SettingsMenuIfReady)) return;
1266    SettingsPopUp(&second);
1267 }
1268
1269 //----------------------------------------------- Load Engine --------------------------------------
1270
1271 char *engineDir, *engineLine, *nickName, *params;
1272 Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick, secondEng;
1273
1274 static void EngSel P((int n, int sel));
1275 static int InstallOK P((int n));
1276
1277 static Option installOptions[] = {
1278 {   0,LR|T2T, 0, NULL, NULL, NULL, NULL, Label, N_("Select engine from list:") },
1279 { 300,LR|TB,200, NULL, (void*) engineMnemonic, (char*) &EngSel, NULL, ListBox, "" },
1280 { 0,SAME_ROW, 0, NULL, NULL, NULL, NULL, Break, NULL },
1281 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("or specify one below:") },
1282 {   0,  0,    0, NULL, (void*) &nickName, NULL, NULL, TextBox, N_("Nickname (optional):") },
1283 {   0,  0,    0, NULL, (void*) &useNick, NULL, NULL, CheckBox, N_("Use nickname in PGN player tags of engine-engine games") },
1284 {   0,  0,    0, NULL, (void*) &engineDir, NULL, NULL, PathName, N_("Engine Directory:") },
1285 {   0,  0,    0, NULL, (void*) &engineName, NULL, NULL, FileName, N_("Engine Command:") },
1286 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("(Directory will be derived from engine path when empty)") },
1287 {   0,  0,    0, NULL, (void*) &isUCI, NULL, NULL, CheckBox, N_("UCI") },
1288 {   0,  0,    0, NULL, (void*) &v1, NULL, NULL, CheckBox, N_("WB protocol v1 (do not wait for engine features)") },
1289 {   0,  0,    0, NULL, (void*) &hasBook, NULL, NULL, CheckBox, N_("Must not use GUI book") },
1290 {   0,  0,    0, NULL, (void*) &addToList, NULL, NULL, CheckBox, N_("Add this engine to the list") },
1291 {   0,  0,    0, NULL, (void*) &storeVariant, NULL, NULL, CheckBox, N_("Force current variant with this engine") },
1292 {   0,  0,    0, NULL, (void*) &InstallOK, "", NULL, EndMark , "" }
1293 };
1294
1295 static int
1296 InstallOK (int n)
1297 {
1298     if(n && (n = SelectedListBoxItem(&installOptions[1])) > 0) { // called by pressing OK, and engine selected
1299         ASSIGN(engineLine, engineList[n]);
1300     }
1301     PopDown(TransientDlg); // early popdown, to allow FreezeUI to instate grab
1302     if(!secondEng) Load(&first, 0); else Load(&second, 1);
1303     return FALSE; // no double PopDown!
1304 }
1305
1306 static void
1307 EngSel (int n, int sel)
1308 {
1309     int nr;
1310     char buf[MSG_SIZ];
1311     if(sel < 1) buf[0] = NULLCHAR; // back to top level
1312     else if(engineList[sel][0] == '#') safeStrCpy(buf, engineList[sel], MSG_SIZ); // group header, open group
1313     else { // normal line, select engine
1314         ASSIGN(engineLine, engineList[sel]);
1315         InstallOK(0);
1316         return;
1317     }
1318     nr = NamesToList(firstChessProgramNames, engineList, engineMnemonic, buf); // replace list by only the group contents
1319     ASSIGN(engineMnemonic[0], buf);
1320     LoadListBox(&installOptions[1], _("# no engines are installed"), -1, -1);
1321     HighlightWithScroll(&installOptions[1], 0, nr);
1322 }
1323
1324 static void
1325 LoadEngineProc (int engineNr, char *title)
1326 {
1327    isUCI = storeVariant = v1 = useNick = False; addToList = hasBook = True; // defaults
1328    secondEng = engineNr;
1329    if(engineLine)   free(engineLine);   engineLine = strdup("");
1330    if(engineDir)    free(engineDir);    engineDir = strdup(".");
1331    if(nickName)     free(nickName);     nickName = strdup("");
1332    if(params)       free(params);       params = strdup("");
1333    ASSIGN(engineMnemonic[0], "");
1334    NamesToList(firstChessProgramNames, engineList, engineMnemonic, "");
1335    GenericPopUp(installOptions, title, TransientDlg, BoardWindow, MODAL, 0);
1336 }
1337
1338 void
1339 LoadEngine1Proc ()
1340 {
1341     LoadEngineProc (0, _("Load first engine"));
1342 }
1343
1344 void
1345 LoadEngine2Proc ()
1346 {
1347     LoadEngineProc (1, _("Load second engine"));
1348 }
1349
1350 //----------------------------------------------------- Edit Book -----------------------------------------
1351
1352 void
1353 EditBookProc ()
1354 {
1355     EditBookEvent();
1356 }
1357
1358 //--------------------------------------------------- New Shuffle Game ------------------------------
1359
1360 static void SetRandom P((int n));
1361
1362 static int
1363 ShuffleOK (int n)
1364 {
1365     ResetGameEvent();
1366     return 1;
1367 }
1368
1369 static Option shuffleOptions[] = {
1370   {   0,  0,    0, NULL, (void*) &shuffleOpenings, NULL, NULL, CheckBox, N_("shuffle") },
1371   { 0,-1,2000000000, NULL, (void*) &appData.defaultFrcPosition, "", NULL, Spin, N_("Start-position number:") },
1372   {   0,  0,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("randomize") },
1373   {   0,  SAME_ROW,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("pick fixed") },
1374   { 0,SAME_ROW, 0, NULL, (void*) &ShuffleOK, "", NULL, EndMark , "" }
1375 };
1376
1377 static void
1378 SetRandom (int n)
1379 {
1380     int r = n==2 ? -1 : random() & (1<<30)-1;
1381     char buf[MSG_SIZ];
1382     snprintf(buf, MSG_SIZ,  "%d", r);
1383     SetWidgetText(&shuffleOptions[1], buf, TransientDlg);
1384     SetWidgetState(&shuffleOptions[0], True);
1385 }
1386
1387 void
1388 ShuffleMenuProc ()
1389 {
1390     GenericPopUp(shuffleOptions, _("New Shuffle Game"), TransientDlg, BoardWindow, MODAL, 0);
1391 }
1392
1393 //------------------------------------------------------ Time Control -----------------------------------
1394
1395 static int TcOK P((int n));
1396 int tmpMoves, tmpTc, tmpInc, tmpOdds1, tmpOdds2, tcType;
1397
1398 static void SetTcType P((int n));
1399
1400 static char *
1401 Value (int n)
1402 {
1403         static char buf[MSG_SIZ];
1404         snprintf(buf, MSG_SIZ, "%d", n);
1405         return buf;
1406 }
1407
1408 static Option tcOptions[] = {
1409 {   0,  0,    0, NULL, (void*) &SetTcType, NULL, NULL, Button, N_("classical") },
1410 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("incremental") },
1411 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("fixed max") },
1412 {   0,  0,  200, NULL, (void*) &tmpMoves, NULL, NULL, Spin, N_("Moves per session:") },
1413 {   0,  0,10000, NULL, (void*) &tmpTc,    NULL, NULL, Spin, N_("Initial time (min):") },
1414 {   0, 0, 10000, NULL, (void*) &tmpInc,   NULL, NULL, Spin, N_("Increment or max (sec/move):") },
1415 {   0,  0,    0, NULL, NULL, NULL, NULL, Label, N_("Time-Odds factors:") },
1416 {   0,  1, 1000, NULL, (void*) &tmpOdds1, NULL, NULL, Spin, N_("Engine #1") },
1417 {   0,  1, 1000, NULL, (void*) &tmpOdds2, NULL, NULL, Spin, N_("Engine #2 / Human") },
1418 {   0,  0,    0, NULL, (void*) &TcOK, "", NULL, EndMark , "" }
1419 };
1420
1421 static int
1422 TcOK (int n)
1423 {
1424     char *tc;
1425     if(tcType == 0 && tmpMoves <= 0) return 0;
1426     if(tcType == 2 && tmpInc <= 0) return 0;
1427     GetWidgetText(&tcOptions[4], &tc); // get original text, in case it is min:sec
1428     searchTime = 0;
1429     switch(tcType) {
1430       case 0:
1431         if(!ParseTimeControl(tc, -1, tmpMoves)) return 0;
1432         appData.movesPerSession = tmpMoves;
1433         ASSIGN(appData.timeControl, tc);
1434         appData.timeIncrement = -1;
1435         break;
1436       case 1:
1437         if(!ParseTimeControl(tc, tmpInc, 0)) return 0;
1438         ASSIGN(appData.timeControl, tc);
1439         appData.timeIncrement = tmpInc;
1440         break;
1441       case 2:
1442         searchTime = tmpInc;
1443     }
1444     appData.firstTimeOdds = first.timeOdds = tmpOdds1;
1445     appData.secondTimeOdds = second.timeOdds = tmpOdds2;
1446     Reset(True, True);
1447     return 1;
1448 }
1449
1450 static void
1451 SetTcType (int n)
1452 {
1453     switch(tcType = n) {
1454       case 0:
1455         SetWidgetText(&tcOptions[3], Value(tmpMoves), TransientDlg);
1456         SetWidgetText(&tcOptions[4], Value(tmpTc), TransientDlg);
1457         SetWidgetText(&tcOptions[5], _("Unused"), TransientDlg);
1458         break;
1459       case 1:
1460         SetWidgetText(&tcOptions[3], _("Unused"), TransientDlg);
1461         SetWidgetText(&tcOptions[4], Value(tmpTc), TransientDlg);
1462         SetWidgetText(&tcOptions[5], Value(tmpInc), TransientDlg);
1463         break;
1464       case 2:
1465         SetWidgetText(&tcOptions[3], _("Unused"), TransientDlg);
1466         SetWidgetText(&tcOptions[4], _("Unused"), TransientDlg);
1467         SetWidgetText(&tcOptions[5], Value(tmpInc), TransientDlg);
1468     }
1469 }
1470
1471 void
1472 TimeControlProc ()
1473 {
1474    tmpMoves = appData.movesPerSession;
1475    tmpInc = appData.timeIncrement; if(tmpInc < 0) tmpInc = 0;
1476    tmpOdds1 = tmpOdds2 = 1; tcType = 0;
1477    tmpTc = atoi(appData.timeControl);
1478    GenericPopUp(tcOptions, _("Time Control"), TransientDlg, BoardWindow, MODAL, 0);
1479    SetTcType(searchTime ? 2 : appData.timeIncrement < 0 ? 0 : 1);
1480 }
1481
1482 //------------------------------- Ask Question -----------------------------------------
1483
1484 int SendReply P((int n));
1485 char pendingReplyPrefix[MSG_SIZ];
1486 ProcRef pendingReplyPR;
1487 char *answer;
1488
1489 Option askOptions[] = {
1490 { 0, 0, 0, NULL, NULL, NULL, NULL, Label,  NULL },
1491 { 0, 0, 0, NULL, (void*) &answer, "", NULL, TextBox, "" },
1492 { 0, 0, 0, NULL, (void*) &SendReply, "", NULL, EndMark , "" }
1493 };
1494
1495 int
1496 SendReply (int n)
1497 {
1498     char buf[MSG_SIZ];
1499     int err;
1500     char *reply=answer;
1501 //    GetWidgetText(&askOptions[1], &reply);
1502     safeStrCpy(buf, pendingReplyPrefix, sizeof(buf)/sizeof(buf[0]) );
1503     if (*buf) strncat(buf, " ", MSG_SIZ - strlen(buf) - 1);
1504     strncat(buf, reply, MSG_SIZ - strlen(buf) - 1);
1505     strncat(buf, "\n",  MSG_SIZ - strlen(buf) - 1);
1506     OutputToProcess(pendingReplyPR, buf, strlen(buf), &err); // does not go into debug file??? => bug
1507     if (err) DisplayFatalError(_("Error writing to chess program"), err, 0);
1508     return TRUE;
1509 }
1510
1511 void
1512 AskQuestion (char *title, char *question, char *replyPrefix, ProcRef pr)
1513 {
1514     safeStrCpy(pendingReplyPrefix, replyPrefix, sizeof(pendingReplyPrefix)/sizeof(pendingReplyPrefix[0]) );
1515     pendingReplyPR = pr;
1516     ASSIGN(answer, "");
1517     askOptions[0].name = question;
1518     if(GenericPopUp(askOptions, title, AskDlg, BoardWindow, MODAL, 0))
1519         AddHandler(&askOptions[1], AskDlg, 2);
1520 }
1521
1522 //---------------------------- Promotion Popup --------------------------------------
1523
1524 static int count;
1525
1526 static void PromoPick P((int n));
1527
1528 static Option promoOptions[] = {
1529 {   0,         0,    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,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1537 {   0, SAME_ROW | NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
1538 };
1539
1540 static void
1541 PromoPick (int n)
1542 {
1543     int promoChar = promoOptions[n+count].value;
1544
1545     PopDown(PromoDlg);
1546
1547     if (promoChar == 0) fromX = -1;
1548     if (fromX == -1) return;
1549
1550     if (! promoChar) {
1551         fromX = fromY = -1;
1552         ClearHighlights();
1553         return;
1554     }
1555     UserMoveEvent(fromX, fromY, toX, toY, promoChar);
1556
1557     if (!appData.highlightLastMove || gotPremove) ClearHighlights();
1558     if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
1559     fromX = fromY = -1;
1560 }
1561
1562 static void
1563 SetPromo (char *name, int nr, char promoChar)
1564 {
1565     ASSIGN(promoOptions[nr].name, name);
1566     promoOptions[nr].value = promoChar;
1567     promoOptions[nr].min = SAME_ROW;
1568 }
1569
1570 void
1571 PromotionPopUp ()
1572 { // choice depends on variant: prepare dialog acordingly
1573   count = 8;
1574   SetPromo(_("Cancel"), --count, 0); // Beware: GenericPopUp cannot handle user buttons named "cancel" (lowe case)!
1575   if(gameInfo.variant != VariantShogi) {
1576     if (!appData.testLegality || gameInfo.variant == VariantSuicide ||
1577         gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove) ||
1578         gameInfo.variant == VariantGiveaway) {
1579       SetPromo(_("King"), --count, 'k');
1580     }
1581     if(gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove)) {
1582       SetPromo(_("Captain"), --count, 'c');
1583       SetPromo(_("Lieutenant"), --count, 'l');
1584       SetPromo(_("General"), --count, 'g');
1585       SetPromo(_("Warlord"), --count, 'w');
1586     } else {
1587       SetPromo(_("Knight"), --count, 'n');
1588       SetPromo(_("Bishop"), --count, 'b');
1589       SetPromo(_("Rook"), --count, 'r');
1590       if(gameInfo.variant == VariantCapablanca ||
1591          gameInfo.variant == VariantGothic ||
1592          gameInfo.variant == VariantCapaRandom) {
1593         SetPromo(_("Archbishop"), --count, 'a');
1594         SetPromo(_("Chancellor"), --count, 'c');
1595       }
1596       SetPromo(_("Queen"), --count, 'q');
1597     }
1598   } else // [HGM] shogi
1599   {
1600       SetPromo(_("Defer"), --count, '=');
1601       SetPromo(_("Promote"), --count, '+');
1602   }
1603   promoOptions[count].min = 0;
1604   GenericPopUp(promoOptions + count, "Promotion", PromoDlg, BoardWindow, NONMODAL, 0);
1605 }
1606
1607 //---------------------------- Chat Windows ----------------------------------------------
1608
1609 static char *line, *memo, *partner, *texts[MAX_CHAT], dirty[MAX_CHAT];
1610 static int activePartner;
1611
1612 void ChatSwitch P((int n));
1613 int  ChatOK P((int n));
1614
1615 Option chatOptions[] = {
1616 { 0,   T_TOP,    100, NULL, (void*) &partner, NULL, NULL, TextBox, N_("Chat partner:") },
1617 { 1, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1618 { 2, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1619 { 3, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1620 { 4, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1621 { 100, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &memo, NULL, NULL, TextBox, "" },
1622 {  0,    0,  510, NULL, (void*) &line, NULL, NULL, TextBox, "" },
1623 { 0, NO_OK|SAME_ROW, 0, NULL, (void*) &ChatOK, NULL, NULL, EndMark , "" }
1624 };
1625
1626 void
1627 OutputChatMessage (int partner, char *mess)
1628 {
1629     char *p = texts[partner];
1630     int len = strlen(mess) + 1;
1631
1632     if(p) len += strlen(p);
1633     texts[partner] = (char*) malloc(len);
1634     snprintf(texts[partner], len, "%s%s", p ? p : "", mess);
1635     FREE(p);
1636     if(partner == activePartner) {
1637         AppendText(&chatOptions[5], mess);
1638         SetInsertPos(&chatOptions[5], len-2);
1639     } else {
1640         SetColor("#FFC000", &chatOptions[partner + (partner < activePartner)]);
1641         dirty[partner] = 1;
1642     }
1643 }
1644
1645 int
1646 ChatOK (int n)
1647 {   // can only be called through <Enter> in chat-partner text-edit, as there is no OK button
1648     char buf[MSG_SIZ];
1649
1650     if(!partner || strcmp(partner, chatPartner[activePartner])) {
1651         safeStrCpy(chatPartner[activePartner], partner, MSG_SIZ);
1652         SetWidgetText(&chatOptions[5], "", -1); // clear text if we alter partner
1653         SetWidgetText(&chatOptions[6], "", ChatDlg); // clear text if we alter partner
1654         HardSetFocus(&chatOptions[6]);
1655     }
1656     if(line[0]) { // something was typed
1657         SetWidgetText(&chatOptions[6], "", ChatDlg);
1658         // from here on it could be back-end
1659         if(line[strlen(line)-1] == '\n') line[strlen(line)-1] = NULLCHAR;
1660         SaveInHistory(line);
1661         if(!strcmp("whispers", chatPartner[activePartner]))
1662               snprintf(buf, MSG_SIZ, "whisper %s\n", line); // WHISPER box uses "whisper" to send
1663         else if(!strcmp("shouts", chatPartner[activePartner]))
1664               snprintf(buf, MSG_SIZ, "shout %s\n", line); // SHOUT box uses "shout" to send
1665         else {
1666             if(!atoi(chatPartner[activePartner])) {
1667                 snprintf(buf, MSG_SIZ, "> %s\n", line); // echo only tells to handle, not channel
1668                 OutputChatMessage(activePartner, buf);
1669                 snprintf(buf, MSG_SIZ, "xtell %s %s\n", chatPartner[activePartner], line);
1670             } else
1671                 snprintf(buf, MSG_SIZ, "tell %s %s\n", chatPartner[activePartner], line);
1672         }
1673         SendToICS(buf);
1674     }
1675     return FALSE; // never pop down
1676 }
1677
1678 void
1679 ChatSwitch (int n)
1680 {
1681     int i, j;
1682     if(n <= activePartner) n--;
1683     activePartner = n;
1684     if(!texts[n]) texts[n] = strdup("");
1685     dirty[n] = 0;
1686     SetWidgetText(&chatOptions[5], texts[n], ChatDlg);
1687     SetInsertPos(&chatOptions[5], strlen(texts[n]));
1688     SetWidgetText(&chatOptions[0], chatPartner[n], ChatDlg);
1689     for(i=j=0; i<MAX_CHAT; i++) {
1690         if(i == activePartner) continue;
1691         SetWidgetLabel(&chatOptions[++j], chatPartner[i]);
1692         SetColor(dirty[i] ? "#FFC000" : "#FFFFFF", &chatOptions[j]);
1693     }
1694     SetWidgetText(&chatOptions[6], "", ChatDlg);
1695     HardSetFocus(&chatOptions[6]);
1696 }
1697
1698 void
1699 ChatProc ()
1700 {
1701     if(GenericPopUp(chatOptions, _("Chat box"), ChatDlg, BoardWindow, NONMODAL, appData.topLevel))
1702         AddHandler(&chatOptions[0], ChatDlg, 2), AddHandler(&chatOptions[6], ChatDlg, 2); // treats return as OK
1703     MarkMenu("View.OpenChatWindow", ChatDlg);
1704 }
1705
1706 //--------------------------------- Game-List options dialog ------------------------------------------
1707
1708 char *strings[LPUSERGLT_SIZE];
1709 int stringPtr;
1710
1711 void
1712 GLT_ClearList ()
1713 {
1714     strings[0] = NULL;
1715     stringPtr = 0;
1716 }
1717
1718 void
1719 GLT_AddToList (char *name)
1720 {
1721     strings[stringPtr++] = name;
1722     strings[stringPtr] = NULL;
1723 }
1724
1725 Boolean
1726 GLT_GetFromList (int index, char *name)
1727 {
1728   safeStrCpy(name, strings[index], MSG_SIZ);
1729   return TRUE;
1730 }
1731
1732 void
1733 GLT_DeSelectList ()
1734 {
1735 }
1736
1737 static void GLT_Button P((int n));
1738 static int GLT_OK P((int n));
1739
1740 static Option listOptions[] = {
1741 {300, LR|TB, 200, NULL, (void*) strings, "", NULL, ListBox, "" }, // For GTK we need to specify a height, as default would just show 3 lines
1742 { 0,    0,     0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("factory") },
1743 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("up") },
1744 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("down") },
1745 { 0, SAME_ROW, 0, NULL, (void*) &GLT_OK, "", NULL, EndMark , "" }
1746 };
1747
1748 static int
1749 GLT_OK (int n)
1750 {
1751     GLT_ParseList();
1752     appData.gameListTags = strdup(lpUserGLT);
1753     return 1;
1754 }
1755
1756 static void
1757 GLT_Button (int n)
1758 {
1759     int index = SelectedListBoxItem (&listOptions[0]);
1760     char *p;
1761     if (index < 0) {
1762         DisplayError(_("No tag selected"), 0);
1763         return;
1764     }
1765     p = strings[index];
1766     if (n == 3) {
1767         if(index >= strlen(GLT_ALL_TAGS)) return;
1768         strings[index] = strings[index+1];
1769         strings[++index] = p;
1770         LoadListBox(&listOptions[0], "?", index, index-1); // only change the two specified entries
1771     } else
1772     if (n == 2) {
1773         if(index == 0) return;
1774         strings[index] = strings[index-1];
1775         strings[--index] = p;
1776         LoadListBox(&listOptions[0], "?", index, index+1);
1777     } else
1778     if (n == 1) {
1779       safeStrCpy(lpUserGLT, GLT_DEFAULT_TAGS, LPUSERGLT_SIZE);
1780       GLT_TagsToList(lpUserGLT);
1781       index = 0;
1782       LoadListBox(&listOptions[0], "?", -1, -1);
1783     }
1784     HighlightListBoxItem(&listOptions[0], index);
1785 }
1786
1787 void
1788 GameListOptionsPopUp (DialogClass parent)
1789 {
1790     safeStrCpy(lpUserGLT, appData.gameListTags, LPUSERGLT_SIZE);
1791     GLT_TagsToList(lpUserGLT);
1792
1793     GenericPopUp(listOptions, _("Game-list options"), TransientDlg, parent, MODAL, 0);
1794 }
1795
1796 void
1797 GameListOptionsProc ()
1798 {
1799     GameListOptionsPopUp(BoardWindow);
1800 }
1801
1802 //----------------------------- Error popup in various uses -----------------------------
1803
1804 /*
1805  * [HGM] Note:
1806  * XBoard has always had some pathologic behavior with multiple simultaneous error popups,
1807  * (which can occur even for modal popups when asynchrounous events, e.g. caused by engine, request a popup),
1808  * and this new implementation reproduces that as well:
1809  * Only the shell of the last instance is remembered in shells[ErrorDlg] (which replaces errorShell),
1810  * so that PopDowns ordered from the code always refer to that instance, and once that is down,
1811  * have no clue as to how to reach the others. For the Delete Window button calling PopDown this
1812  * has now been repaired, as the action routine assigned to it gets the shell passed as argument.
1813  */
1814
1815 int errorUp = False;
1816
1817 void
1818 ErrorPopDown ()
1819 {
1820     if (!errorUp) return;
1821     dialogError = errorUp = False;
1822     PopDown(ErrorDlg); PopDown(FatalDlg); // on explicit request we pop down any error dialog
1823     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
1824 }
1825
1826 static int
1827 ErrorOK (int n)
1828 {
1829     dialogError = errorUp = False;
1830     PopDown(n == 1 ? FatalDlg : ErrorDlg); // kludge: non-modal dialogs have one less (dummy) option
1831     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
1832     return FALSE; // prevent second Popdown !
1833 }
1834
1835 static Option errorOptions[] = {
1836 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // dummy option: will never be displayed
1837 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // textValue field will be set before popup
1838 { 0,NO_CANCEL,0, NULL, (void*) &ErrorOK, "", NULL, EndMark , "" }
1839 };
1840
1841 void
1842 ErrorPopUp (char *title, char *label, int modal)
1843 {
1844     errorUp = True;
1845     errorOptions[1].name = label;
1846     if(dialogError = shellUp[TransientDlg])
1847         GenericPopUp(errorOptions+1, title, FatalDlg, TransientDlg, MODAL, 0); // pop up as daughter of the transient dialog
1848     else
1849         GenericPopUp(errorOptions+modal, title, modal ? FatalDlg: ErrorDlg, BoardWindow, modal, 0); // kludge: option start address indicates modality
1850 }
1851
1852 void
1853 DisplayError (String message, int error)
1854 {
1855     char buf[MSG_SIZ];
1856
1857     if (error == 0) {
1858         if (appData.debugMode || appData.matchMode) {
1859             fprintf(stderr, "%s: %s\n", programName, message);
1860         }
1861     } else {
1862         if (appData.debugMode || appData.matchMode) {
1863             fprintf(stderr, "%s: %s: %s\n",
1864                     programName, message, strerror(error));
1865         }
1866         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
1867         message = buf;
1868     }
1869     ErrorPopUp(_("Error"), message, FALSE);
1870 }
1871
1872
1873 void
1874 DisplayMoveError (String message)
1875 {
1876     fromX = fromY = -1;
1877     ClearHighlights();
1878     DrawPosition(TRUE, NULL); // selective redraw would miss the from-square of the rejected move, displayed empty after drag, but not marked damaged!
1879     if (appData.debugMode || appData.matchMode) {
1880         fprintf(stderr, "%s: %s\n", programName, message);
1881     }
1882     if (appData.popupMoveErrors) {
1883         ErrorPopUp(_("Error"), message, FALSE);
1884     } else {
1885         DisplayMessage(message, "");
1886     }
1887 }
1888
1889
1890 void
1891 DisplayFatalError (String message, int error, int status)
1892 {
1893     char buf[MSG_SIZ];
1894
1895     errorExitStatus = status;
1896     if (error == 0) {
1897         fprintf(stderr, "%s: %s\n", programName, message);
1898     } else {
1899         fprintf(stderr, "%s: %s: %s\n",
1900                 programName, message, strerror(error));
1901         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
1902         message = buf;
1903     }
1904     if(mainOptions[W_BOARD].handle) {
1905         if (appData.popupExitMessage) {
1906             ErrorPopUp(status ? _("Fatal Error") : _("Exiting"), message, TRUE);
1907         } else {
1908             ExitEvent(status);
1909         }
1910     }
1911 }
1912
1913 void
1914 DisplayInformation (String message)
1915 {
1916     ErrorPopDown();
1917     ErrorPopUp(_("Information"), message, TRUE);
1918 }
1919
1920 void
1921 DisplayNote (String message)
1922 {
1923     ErrorPopDown();
1924     ErrorPopUp(_("Note"), message, FALSE);
1925 }
1926
1927 void
1928 DisplayTitle (char *text)
1929 {
1930     char title[MSG_SIZ];
1931     char icon[MSG_SIZ];
1932
1933     if (text == NULL) text = "";
1934
1935     if(partnerUp) { SetDialogTitle(DummyDlg, text); return; }
1936
1937     if (*text != NULLCHAR) {
1938       safeStrCpy(icon, text, sizeof(icon)/sizeof(icon[0]) );
1939       safeStrCpy(title, text, sizeof(title)/sizeof(title[0]) );
1940     } else if (appData.icsActive) {
1941         snprintf(icon, sizeof(icon), "%s", appData.icsHost);
1942         snprintf(title, sizeof(title), "%s: %s", programName, appData.icsHost);
1943     } else if (appData.cmailGameName[0] != NULLCHAR) {
1944         snprintf(icon, sizeof(icon), "%s", "CMail");
1945         snprintf(title,sizeof(title), "%s: %s", programName, "CMail");
1946 #ifdef GOTHIC
1947     // [HGM] license: This stuff should really be done in back-end, but WinBoard already had a pop-up for it
1948     } else if (gameInfo.variant == VariantGothic) {
1949       safeStrCpy(icon,  programName, sizeof(icon)/sizeof(icon[0]) );
1950       safeStrCpy(title, GOTHIC,     sizeof(title)/sizeof(title[0]) );
1951 #endif
1952 #ifdef FALCON
1953     } else if (gameInfo.variant == VariantFalcon) {
1954       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
1955       safeStrCpy(title, FALCON, sizeof(title)/sizeof(title[0]) );
1956 #endif
1957     } else if (appData.noChessProgram) {
1958       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
1959       safeStrCpy(title, programName, sizeof(title)/sizeof(title[0]) );
1960     } else {
1961       safeStrCpy(icon, first.tidy, sizeof(icon)/sizeof(icon[0]) );
1962         snprintf(title,sizeof(title), "%s: %s", programName, first.tidy);
1963     }
1964     SetWindowTitle(text, title, icon);
1965 }
1966
1967 #define PAUSE_BUTTON "P"
1968 #define PIECE_MENU_SIZE 18
1969 static String pieceMenuStrings[2][PIECE_MENU_SIZE+1] = {
1970     { N_("White"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
1971       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
1972       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
1973       N_("Empty square"), N_("Clear board"), NULL },
1974     { N_("Black"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
1975       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
1976       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
1977       N_("Empty square"), N_("Clear board"), NULL }
1978 };
1979 /* must be in same order as pieceMenuStrings! */
1980 static ChessSquare pieceMenuTranslation[2][PIECE_MENU_SIZE] = {
1981     { WhitePlay, (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
1982         WhiteRook, WhiteQueen, WhiteKing, (ChessSquare) 0, WhiteAlfil,
1983         WhiteCannon, WhiteAngel, WhiteMarshall, (ChessSquare) 0,
1984         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
1985     { BlackPlay, (ChessSquare) 0, BlackPawn, BlackKnight, BlackBishop,
1986         BlackRook, BlackQueen, BlackKing, (ChessSquare) 0, BlackAlfil,
1987         BlackCannon, BlackAngel, BlackMarshall, (ChessSquare) 0,
1988         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
1989 };
1990
1991 #define DROP_MENU_SIZE 6
1992 static String dropMenuStrings[DROP_MENU_SIZE+1] = {
1993     "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"), N_("Queen"), NULL
1994   };
1995 /* must be in same order as dropMenuStrings! */
1996 static ChessSquare dropMenuTranslation[DROP_MENU_SIZE] = {
1997     (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
1998     WhiteRook, WhiteQueen
1999 };
2000
2001 // [HGM] experimental code to pop up window just like the main window, using GenercicPopUp
2002
2003 static Option *Exp P((int n, int x, int y));
2004 void MenuCallback P((int n));
2005 void SizeKludge P((int n));
2006 static Option *LogoW P((int n, int x, int y));
2007 static Option *LogoB P((int n, int x, int y));
2008
2009 static int pmFromX = -1, pmFromY = -1;
2010 void *userLogo;
2011
2012 void
2013 DisplayLogos (Option *w1, Option *w2)
2014 {
2015         void *whiteLogo = first.programLogo, *blackLogo = second.programLogo;
2016         if(appData.autoLogo) {
2017
2018           switch(gameMode) { // pick logos based on game mode
2019             case IcsObserving:
2020                 whiteLogo = second.programLogo; // ICS logo
2021                 blackLogo = second.programLogo;
2022             default:
2023                 break;
2024             case IcsPlayingWhite:
2025                 if(!appData.zippyPlay) whiteLogo = userLogo;
2026                 blackLogo = second.programLogo; // ICS logo
2027                 break;
2028             case IcsPlayingBlack:
2029                 whiteLogo = second.programLogo; // ICS logo
2030                 blackLogo = appData.zippyPlay ? first.programLogo : userLogo;
2031                 break;
2032             case TwoMachinesPlay:
2033                 if(first.twoMachinesColor[0] == 'b') {
2034                     whiteLogo = second.programLogo;
2035                     blackLogo = first.programLogo;
2036                 }
2037                 break;
2038             case MachinePlaysWhite:
2039                 blackLogo = userLogo;
2040                 break;
2041             case MachinePlaysBlack:
2042                 whiteLogo = userLogo;
2043                 blackLogo = first.programLogo;
2044           }
2045         }
2046         DrawLogo(w1, whiteLogo);
2047         DrawLogo(w2, blackLogo);
2048 }
2049
2050 static void
2051 PMSelect (int n)
2052 {   // user callback for board context menus
2053     if (pmFromX < 0 || pmFromY < 0) return;
2054     if(n == W_DROP) DropMenuEvent(dropMenuTranslation[values[n]], pmFromX, pmFromY);
2055     else EditPositionMenuEvent(pieceMenuTranslation[n - W_MENUW][values[n]], pmFromX, pmFromY);
2056 }
2057
2058 static void
2059 CCB (int n)
2060 {
2061     shiftKey = (ShiftKeys() & 3) != 0;
2062     if(n < 0) { // button != 1
2063         n = -n;
2064         if(shiftKey && (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack)) {
2065             AdjustClock(n == W_BLACK, 1);
2066         }
2067     } else
2068     ClockClick(n == W_BLACK);
2069 }
2070
2071 Option mainOptions[] = { // description of main window in terms of generic dialog creator
2072 { 0, 0xCA, 0, NULL, NULL, "", NULL, BarBegin, "" }, // menu bar
2073   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("File") },
2074   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Edit") },
2075   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("View") },
2076   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Mode") },
2077   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Action") },
2078   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Engine") },
2079   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Options") },
2080   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Help") },
2081 { 0, 0, 0, NULL, (void*)&SizeKludge, "", NULL, BarEnd, "" },
2082 { 0, LR|T2T|BORDER|SAME_ROW, 0, NULL, NULL, "", NULL, Label, "1" }, // optional title in window
2083 { 50,    LL|TT,            100, NULL, (void*) &LogoW, NULL, NULL, -1, "" }, // white logo
2084 { 12,   L2L|T2T,           200, NULL, (void*) &CCB, NULL, NULL, Label, "White" }, // white clock
2085 { 13,   R2R|T2T|SAME_ROW,  200, NULL, (void*) &CCB, NULL, NULL, Label, "Black" }, // black clock
2086 { 50,    RR|TT|SAME_ROW,   100, NULL, (void*) &LogoB, NULL, NULL, -1, "" }, // black logo
2087 { 0, LR|T2T|BORDER,        401, NULL, NULL, "", NULL, -1, "2" }, // backup for title in window (if no room for other)
2088 { 0, LR|T2T|BORDER,        270, NULL, NULL, "", NULL, Label, "message" }, // message field
2089 { 0, RR|TT|SAME_ROW,       125, NULL, NULL, "", NULL, BoxBegin, "" }, // (optional) button bar
2090   { 0,    0,     0, NULL, (void*) &ToStartEvent, NULL, NULL, Button, N_("<<") },
2091   { 0, SAME_ROW, 0, NULL, (void*) &BackwardEvent, NULL, NULL, Button, N_("<") },
2092   { 0, SAME_ROW, 0, NULL, (void*) &PauseEvent, NULL, NULL, Button, N_(PAUSE_BUTTON) },
2093   { 0, SAME_ROW, 0, NULL, (void*) &ForwardEvent, NULL, NULL, Button, N_(">") },
2094   { 0, SAME_ROW, 0, NULL, (void*) &ToEndEvent, NULL, NULL, Button, N_(">>") },
2095 { 0, 0, 0, NULL, NULL, "", NULL, BoxEnd, "" },
2096 { 401, LR|TB, 401, NULL, (char*) &Exp, NULL, NULL, Graph, "shadow board" }, // board
2097   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[0], PopUp, "menuW" },
2098   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[1], PopUp, "menuB" },
2099   { -1, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, dropMenuStrings, PopUp, "menuD" },
2100 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2101 };
2102
2103 Option *
2104 LogoW (int n, int x, int y)
2105 {
2106     if(n == 10) DisplayLogos(&mainOptions[W_WHITE-1], NULL);
2107     return NULL;
2108 }
2109
2110 Option *
2111 LogoB (int n, int x, int y)
2112 {
2113     if(n == 10) DisplayLogos(NULL, &mainOptions[W_BLACK+1]);
2114     return NULL;
2115 }
2116
2117 void
2118 SizeKludge (int n)
2119 {   // callback called by GenericPopUp immediately after sizing the menu bar
2120     int width = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2121     int w = width - 44 - mainOptions[n].min;
2122     mainOptions[W_TITLE].max = w; // width left behind menu bar
2123     if(w < 0.4*width) // if no reasonable amount of space for title, force small layout
2124         mainOptions[W_SMALL].type = mainOptions[W_TITLE].type, mainOptions[W_TITLE].type = -1;
2125 }
2126
2127 void
2128 MenuCallback (int n)
2129 {
2130     MenuProc *proc = (MenuProc *) (((MenuItem*)(mainOptions[n].choice))[values[n]].proc);
2131
2132     if(!proc) RecentEngineEvent(values[n] - firstEngineItem); else (proc)();
2133 }
2134
2135 static Option *
2136 Exp (int n, int x, int y)
2137 {
2138     static int but1, but3, oldW, oldH;
2139     int menuNr = -3, sizing;
2140
2141     if(n == 0) { // motion
2142         if(SeekGraphClick(Press, x, y, 1)) return NULL;
2143         if(but1 && !PromoScroll(x, y)) DragPieceMove(x, y);
2144         if(but3) MovePV(x, y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
2145         return NULL;
2146     }
2147     if(n != 10 && PopDown(PromoDlg)) fromX = fromY = -1; // user starts fiddling with board when promotion dialog is up
2148     shiftKey = ShiftKeys();
2149     controlKey = (shiftKey & 0xC) != 0;
2150     shiftKey = (shiftKey & 3) != 0;
2151     switch(n) {
2152         case  1: LeftClick(Press,   x, y), but1 = 1; break;
2153         case -1: LeftClick(Release, x, y), but1 = 0; break;
2154         case  2: shiftKey = !shiftKey;
2155         case  3: menuNr = RightClick(Press,   x, y, &pmFromX, &pmFromY), but3 = 1; break;
2156         case -2: shiftKey = !shiftKey;
2157         case -3: menuNr = RightClick(Release, x, y, &pmFromX, &pmFromY), but3 = 0; break;
2158         case 10:
2159             sizing = (oldW != x || oldH != y);
2160             oldW = x; oldH = y;
2161             InitDrawingHandle(mainOptions + W_BOARD);
2162             if(sizing) return NULL; // don't redraw while sizing
2163             DrawPosition(True, NULL);
2164         default:
2165             return NULL;
2166     }
2167
2168     switch(menuNr) {
2169       case 0: return &mainOptions[shiftKey ? W_MENUW: W_MENUB];
2170       case 1: SetupDropMenu(); return &mainOptions[W_DROP];
2171       case 2:
2172       case -1: ErrorPopDown();
2173       case -2:
2174       default: break; // -3, so no clicks caught
2175     }
2176     return NULL;
2177 }
2178
2179 Option *
2180 BoardPopUp (int squareSize, int lineGap, void *clockFontThingy)
2181 {
2182     int i, size = BOARD_WIDTH*(squareSize + lineGap) + lineGap, logo = appData.logoSize;
2183     mainOptions[W_WHITE].choice = (char**) clockFontThingy;
2184     mainOptions[W_BLACK].choice = (char**) clockFontThingy;
2185     mainOptions[W_BOARD].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2186     mainOptions[W_BOARD].max = mainOptions[W_SMALL].max = size; // board size
2187     mainOptions[W_SMALL].max = size - 2; // board title (subtract border!)
2188     mainOptions[W_BLACK].max = mainOptions[W_WHITE].max = size/2-3; // clock width
2189     mainOptions[W_MESSG].max = appData.showButtonBar ? size-135 : size-2; // message
2190     mainOptions[W_MENU].max = size-40; // menu bar
2191     mainOptions[W_TITLE].type = appData.titleInWindow ? Label : -1 ;
2192     if(logo && logo <= size/4) { // Activate logos
2193         mainOptions[W_WHITE-1].type = mainOptions[W_BLACK+1].type = Graph;
2194         mainOptions[W_WHITE-1].max  = mainOptions[W_BLACK+1].max  = logo;
2195         mainOptions[W_WHITE-1].value= mainOptions[W_BLACK+1].value= logo/2;
2196         mainOptions[W_WHITE].min  |= SAME_ROW;
2197         mainOptions[W_WHITE].max  = mainOptions[W_BLACK].max  -= logo + 4;
2198         mainOptions[W_WHITE].name = mainOptions[W_BLACK].name = "Double\nHeight";
2199     }
2200     if(!appData.showButtonBar) for(i=W_BUTTON; i<W_BOARD; i++) mainOptions[i].type = -1;
2201     for(i=0; i<8; i++) mainOptions[i+1].choice = (char**) menuBar[i].mi;
2202     AppendEnginesToMenu(appData.recentEngineList);
2203     GenericPopUp(mainOptions, "XBoard", BoardWindow, BoardWindow, NONMODAL, 1); // allways top-level
2204     return mainOptions;
2205 }
2206
2207 static Option *
2208 SlaveExp (int n, int x, int y)
2209 {
2210     if(n == 10) { // expose event
2211         flipView = !flipView; partnerUp = !partnerUp;
2212         DrawPosition(True, NULL); // [HGM] dual: draw other board in other orientation
2213         flipView = !flipView; partnerUp = !partnerUp;
2214     }
2215     return NULL;
2216 }
2217
2218 Option dualOptions[] = { // auxiliary board window
2219 { 0, L2L|T2T,              198, NULL, NULL, NULL, NULL, Label, "White" }, // white clock
2220 { 0, R2R|T2T|SAME_ROW,     198, NULL, NULL, NULL, NULL, Label, "Black" }, // black clock
2221 { 0, LR|T2T|BORDER,        401, NULL, NULL, NULL, NULL, Label, "This feature is experimental" }, // message field
2222 { 401, LR|TT, 401, NULL, (char*) &SlaveExp, NULL, NULL, Graph, "shadow board" }, // board
2223 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2224 };
2225
2226 void
2227 SlavePopUp ()
2228 {
2229     int size = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2230     // copy params from main board
2231     dualOptions[0].choice = mainOptions[W_WHITE].choice;
2232     dualOptions[1].choice = mainOptions[W_BLACK].choice;
2233     dualOptions[3].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2234     dualOptions[3].max = dualOptions[2].max = size; // board width
2235     dualOptions[0].max = dualOptions[1].max = size/2 - 3; // clock width
2236     GenericPopUp(dualOptions, "XBoard", DummyDlg, BoardWindow, NONMODAL, appData.topLevel);
2237     SlaveResize(dualOptions+3);
2238 }
2239
2240 void
2241 DisplayWhiteClock (long timeRemaining, int highlight)
2242 {
2243     if(appData.noGUI) return;
2244     if(twoBoards && partnerUp) {
2245         DisplayTimerLabel(&dualOptions[0], _("White"), timeRemaining, highlight);
2246         return;
2247     }
2248     DisplayTimerLabel(&mainOptions[W_WHITE], _("White"), timeRemaining, highlight);
2249     if(highlight) SetClockIcon(0);
2250 }
2251
2252 void
2253 DisplayBlackClock (long timeRemaining, int highlight)
2254 {
2255     if(appData.noGUI) return;
2256     if(twoBoards && partnerUp) {
2257         DisplayTimerLabel(&dualOptions[1], _("Black"), timeRemaining, highlight);
2258         return;
2259     }
2260     DisplayTimerLabel(&mainOptions[W_BLACK], _("Black"), timeRemaining, highlight);
2261     if(highlight) SetClockIcon(1);
2262 }
2263
2264
2265 //---------------------------------------------
2266
2267 void
2268 DisplayMessage (char *message, char *extMessage)
2269 {
2270   /* display a message in the message widget */
2271
2272   char buf[MSG_SIZ];
2273
2274   if (extMessage)
2275     {
2276       if (*message)
2277         {
2278           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
2279           message = buf;
2280         }
2281       else
2282         {
2283           message = extMessage;
2284         };
2285     };
2286
2287     safeStrCpy(lastMsg, message, MSG_SIZ); // [HGM] make available
2288
2289   /* need to test if messageWidget already exists, since this function
2290      can also be called during the startup, if for example a Xresource
2291      is not set up correctly */
2292   if(mainOptions[W_MESSG].handle)
2293     SetWidgetLabel(&mainOptions[W_MESSG], message);
2294
2295   return;
2296 }
2297
2298 //----------------------------------- File Browser -------------------------------
2299
2300 #ifdef HAVE_DIRENT_H
2301 #include <dirent.h>
2302 #else
2303 #include <sys/dir.h>
2304 #define dirent direct
2305 #endif
2306
2307 #include <sys/stat.h>
2308
2309 #define MAXFILES 1000
2310
2311 static ChessProgramState *savCps;
2312 static FILE **savFP;
2313 static char *fileName, *extFilter, *savMode, **namePtr;
2314 static int folderPtr, filePtr, oldVal, byExtension, extFlag, pageStart, cnt;
2315 static char curDir[MSG_SIZ], title[MSG_SIZ], *folderList[MAXFILES], *fileList[MAXFILES];
2316
2317 static char *FileTypes[] = {
2318 "Chess Games",
2319 "Chess Positions",
2320 "Tournaments",
2321 "Opening Books",
2322 "Sound files",
2323 "Images",
2324 "Settings (*.ini)",
2325 "Log files",
2326 "All files",
2327 NULL,
2328 "PGN",
2329 "Old-Style Games",
2330 "FEN",
2331 "Old-Style Positions",
2332 NULL,
2333 NULL
2334 };
2335
2336 static char *Extensions[] = {
2337 ".pgn .game",
2338 ".fen .epd .pos",
2339 ".trn",
2340 ".bin",
2341 ".wav",
2342 ".xpm",
2343 ".ini",
2344 ".log",
2345 "",
2346 "INVALID",
2347 ".pgn",
2348 ".game",
2349 ".fen",
2350 ".pos",
2351 NULL,
2352 ""
2353 };
2354
2355 void DirSelProc P((int n, int sel));
2356 void FileSelProc P((int n, int sel));
2357 void SetTypeFilter P((int n));
2358 int BrowseOK P((int n));
2359 void Switch P((int n));
2360 void CreateDir P((int n));
2361
2362 Option browseOptions[] = {
2363 {   0,    LR|T2T,      500, NULL, NULL, NULL, NULL, Label, title },
2364 {   0,    L2L|T2T,     250, NULL, NULL, NULL, NULL, Label, N_("Directories:") },
2365 {   0,R2R|T2T|SAME_ROW,100, NULL, NULL, NULL, NULL, Label, N_("Files:") },
2366 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by name") },
2367 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by type") },
2368 { 300,    L2L|TB,      250, NULL, (void*) folderList, (char*) &DirSelProc, NULL, ListBox, "" },
2369 { 300, R2R|TB|SAME_ROW,250, NULL, (void*) fileList, (char*) &FileSelProc, NULL, ListBox, "" },
2370 {   0,       0,        300, NULL, (void*) &fileName, NULL, NULL, TextBox, N_("Filename:") },
2371 {   0,    SAME_ROW,    120, NULL, (void*) &CreateDir, NULL, NULL, Button, N_("New directory") },
2372 {   0, COMBO_CALLBACK, 150, NULL, (void*) &SetTypeFilter, NULL, FileTypes, ComboBox, N_("File type:") },
2373 {   0,    SAME_ROW,      0, NULL, (void*) &BrowseOK, "", NULL, EndMark , "" }
2374 };
2375
2376 int
2377 BrowseOK (int n)
2378 {
2379         if(!fileName[0]) { // it is enough to have a file selected
2380             if(browseOptions[6].textValue) { // kludge: if callback specified we browse for file
2381                 int sel = SelectedListBoxItem(&browseOptions[6]);
2382                 if(sel < 0 || sel >= filePtr) return FALSE;
2383                 ASSIGN(fileName, fileList[sel]);
2384             } else { // we browse for path
2385                 ASSIGN(fileName, curDir); // kludge: without callback we browse for path
2386             }
2387         }
2388         if(!fileName[0]) return FALSE; // refuse OK when no file
2389         if(!savMode[0]) { // browsing for name only (dialog Browse button)
2390                 if(fileName[0] == '/') // We already had a path name
2391                     snprintf(title, MSG_SIZ, "%s", fileName);
2392                 else
2393                     snprintf(title, MSG_SIZ, "%s/%s", curDir, fileName);
2394                 SetWidgetText((Option*) savFP, title, TransientDlg);
2395                 currentCps = savCps; // could return to Engine Settings dialog!
2396                 return TRUE;
2397         }
2398         *savFP = fopen(fileName, savMode);
2399         if(*savFP == NULL) return FALSE; // refuse OK if file not openable
2400         ASSIGN(*namePtr, fileName);
2401         ScheduleDelayedEvent(DelayedLoad, 50);
2402         currentCps = savCps; // not sure this is ever non-null
2403         return TRUE;
2404 }
2405
2406 int
2407 AlphaNumCompare (char *p, char *q)
2408 {
2409     while(*p) {
2410         if(isdigit(*p) && isdigit(*q) && atoi(p) != atoi(q))
2411              return (atoi(p) > atoi(q) ? 1 : -1);
2412         if(*p != *q) break;
2413         p++, q++;
2414     }
2415     if(*p == *q) return 0;
2416     return (*p > *q ? 1 : -1);
2417 }
2418
2419 int
2420 Comp (const void *s, const void *t)
2421 {
2422     char *p = *(char**) s, *q = *(char**) t;
2423     if(extFlag) {
2424         char *h; int r;
2425         while(h = strchr(p, '.')) p = h+1;
2426         if(p == *(char**) s) p = "";
2427         while(h = strchr(q, '.')) q = h+1;
2428         if(q == *(char**) t) q = "";
2429         r = AlphaNumCompare(p, q);
2430         if(r) return r;
2431     }
2432     return AlphaNumCompare( *(char**) s, *(char**) t );
2433 }
2434
2435 void
2436 ListDir (int pathFlag)
2437 {
2438         DIR *dir;
2439         struct dirent *dp;
2440         struct stat statBuf;
2441         static int lastFlag;
2442
2443         if(pathFlag < 0) pathFlag = lastFlag;
2444         lastFlag = pathFlag;
2445         dir = opendir(".");
2446         getcwd(curDir, MSG_SIZ);
2447         snprintf(title, MSG_SIZ, "%s   %s", _("Contents of"), curDir);
2448         folderPtr = filePtr = cnt = 0; // clear listing
2449
2450         while (dp = readdir(dir)) { // pass 1: list foders
2451             char *s = dp->d_name;
2452             if(!stat(s, &statBuf) && S_ISDIR(statBuf.st_mode)) { // stat succeeds and tells us it is directory
2453                 if(s[0] == '.' && strcmp(s, "..")) continue; // suppress hidden, except ".."
2454                 ASSIGN(folderList[folderPtr], s); if(folderPtr < MAXFILES-2) folderPtr++;
2455             } else if(!pathFlag) {
2456                 char *s = dp->d_name, match=0;
2457 //              if(cnt == pageStart) { ASSIGN }
2458                 if(s[0] == '.') continue; // suppress hidden files
2459                 if(extFilter[0]) { // [HGM] filter on extension
2460                     char *p = extFilter, *q;
2461                     do {
2462                         if(q = strchr(p, ' ')) *q = 0;
2463                         if(strstr(s, p)) match++;
2464                         if(q) *q = ' ';
2465                     } while(q && (p = q+1));
2466                     if(!match) continue;
2467                 }
2468                 if(filePtr == MAXFILES-2) continue;
2469                 if(cnt++ < pageStart) continue;
2470                 ASSIGN(fileList[filePtr], s); filePtr++;
2471             }
2472         }
2473         if(filePtr == MAXFILES-2) { ASSIGN(fileList[filePtr], _("  next page")); filePtr++; }
2474         FREE(folderList[folderPtr]); folderList[folderPtr] = NULL;
2475         FREE(fileList[filePtr]); fileList[filePtr] = NULL;
2476         closedir(dir);
2477         extFlag = 0;         qsort((void*)folderList, folderPtr, sizeof(char*), &Comp);
2478         extFlag = byExtension; qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
2479 }
2480
2481 void
2482 Refresh (int pathFlag)
2483 {
2484     ListDir(pathFlag); // and make new one
2485     LoadListBox(&browseOptions[5], "", -1, -1);
2486     LoadListBox(&browseOptions[6], "", -1, -1);
2487     SetWidgetLabel(&browseOptions[0], title);
2488 }
2489
2490 static char msg1[] = N_("FIRST TYPE DIRECTORY NAME HERE");
2491 static char msg2[] = N_("TRY ANOTHER NAME");
2492
2493 void
2494 CreateDir (int n)
2495 {
2496     char *name, *errmsg = "";
2497     GetWidgetText(&browseOptions[n-1], &name);
2498     if(!strcmp(name, msg1) || !strcmp(name, msg2)) return;
2499     if(!name[0]) errmsg = _(msg1); else
2500     if(mkdir(name, 0755)) errmsg = _(msg2);
2501     else {
2502         chdir(name);
2503         Refresh(-1);
2504     }
2505     SetWidgetText(&browseOptions[n-1], errmsg, BrowserDlg);
2506 }
2507
2508 void
2509 Switch (int n)
2510 {
2511     if(byExtension == (n == 4)) return;
2512     extFlag = byExtension = (n == 4);
2513     qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
2514     LoadListBox(&browseOptions[6], "", -1, -1);
2515 }
2516
2517 void
2518 SetTypeFilter (int n)
2519 {
2520     int j = values[n];
2521     if(j == browseOptions[n].value) return; // no change
2522     browseOptions[n].value = j;
2523     SetWidgetLabel(&browseOptions[n], FileTypes[j]);
2524     ASSIGN(extFilter, Extensions[j]);
2525     pageStart = 0;
2526     Refresh(-1); // uses pathflag remembered by ListDir
2527     values[n] = oldVal; // do not disturb combo settings of underlying dialog
2528 }
2529
2530 void
2531 FileSelProc (int n, int sel)
2532 {
2533     if(sel < 0 || fileList[sel] == NULL) return;
2534     if(sel == MAXFILES-2) { pageStart = cnt; Refresh(-1); return; }
2535     ASSIGN(fileName, fileList[sel]);
2536     if(BrowseOK(0)) PopDown(BrowserDlg);
2537 }
2538
2539 void
2540 DirSelProc (int n, int sel)
2541 {
2542     if(!chdir(folderList[sel])) { // cd succeeded, so we are in new directory now
2543         Refresh(-1);
2544     }
2545 }
2546
2547 void
2548 Browse (DialogClass dlg, char *label, char *proposed, char *ext, Boolean pathFlag, char *mode, char **name, FILE **fp)
2549 {
2550     int j=0;
2551     savFP = fp; savMode = mode, namePtr = name, savCps = currentCps, oldVal = values[9]; // save params, for use in callback
2552     ASSIGN(extFilter, ext);
2553     ASSIGN(fileName, proposed ? proposed : "");
2554     for(j=0; Extensions[j]; j++) // look up actual value in list of possible values, to get selection nr
2555         if(extFilter && !strcmp(extFilter, Extensions[j])) break;
2556     if(Extensions[j] == NULL) { j++; ASSIGN(FileTypes[j], extFilter); }
2557     browseOptions[9].value = j;
2558     browseOptions[6].textValue = (char*) (pathFlag ? NULL : &FileSelProc); // disable file listbox during path browsing
2559     pageStart = 0; ListDir(pathFlag);
2560     currentCps = NULL;
2561     GenericPopUp(browseOptions, label, BrowserDlg, dlg, MODAL, 0);
2562     SetWidgetLabel(&browseOptions[9], FileTypes[j]);
2563 }
2564
2565 static char *openName;
2566 FileProc fileProc;
2567 char *fileOpenMode;
2568 FILE *openFP;
2569
2570 void
2571 DelayedLoad ()
2572 {
2573   (void) (*fileProc)(openFP, 0, openName);
2574 }
2575
2576 void
2577 FileNamePopUp (char *label, char *def, char *filter, FileProc proc, char *openMode)
2578 {
2579     fileProc = proc;            /* I can't see a way not */
2580     fileOpenMode = openMode;    /*   to use globals here */
2581     FileNamePopUpWrapper(label, def, filter, proc, False, openMode, &openName, &openFP);
2582 }
2583
2584 void
2585 ActivateTheme (int col)
2586 {
2587 }
2588
2589 char *
2590 Col2Text (int n)
2591 {
2592     return NULL;
2593 }