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