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