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