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