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