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