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