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