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