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