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