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