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