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