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