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