Use different tables for different dialog columns
[xboard.git] / dialogs.c
1 /*
2  * dialogs.c -- platform-independent code for dialogs of XBoard
3  *
4  * Copyright 2000, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
5  * ------------------------------------------------------------------------
6  *
7  * GNU XBoard is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or (at
10  * your option) any later version.
11  *
12  * GNU XBoard is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program. If not, see http://www.gnu.org/licenses/.  *
19  *
20  *------------------------------------------------------------------------
21  ** See the file ChangeLog for a revision history.  */
22
23 // [HGM] this file is the counterpart of woptions.c, containing xboard popup menus
24 // similar to those of WinBoard, to set the most common options interactively.
25
26 #include "config.h"
27
28 #include <stdio.h>
29 #include <ctype.h>
30 #include <errno.h>
31 #include <sys/types.h>
32
33 #if STDC_HEADERS
34 # include <stdlib.h>
35 # include <string.h>
36 #else /* not STDC_HEADERS */
37 extern char *getenv();
38 # if HAVE_STRING_H
39 #  include <string.h>
40 # else /* not HAVE_STRING_H */
41 #  include <strings.h>
42 # endif /* not HAVE_STRING_H */
43 #endif /* not STDC_HEADERS */
44
45 #if HAVE_UNISTD_H
46 # include <unistd.h>
47 #endif
48 #include <stdint.h>
49
50 #include "common.h"
51 #include "frontend.h"
52 #include "backend.h"
53 #include "xboard2.h"
54 #include "menus.h"
55 #include "dialogs.h"
56 #include "gettext.h"
57
58 #ifdef ENABLE_NLS
59 # define  _(s) gettext (s)
60 # define N_(s) gettext_noop (s)
61 #else
62 # define  _(s) (s)
63 # define N_(s)  s
64 #endif
65
66
67 int values[MAX_OPTIONS];
68 ChessProgramState *currentCps;
69
70 //----------------------------Generic dialog --------------------------------------------
71
72 // cloned from Engine Settings dialog (and later merged with it)
73
74 char *marked[NrOfDialogs];
75 Boolean shellUp[NrOfDialogs];
76
77 void
78 MarkMenu (char *item, int dlgNr)
79 {
80     MarkMenuItem(marked[dlgNr] = item, True);
81 }
82
83 void
84 AddLine (Option *opt, char *s)
85 {
86     AppendText(opt, s);
87     AppendText(opt, "\n");
88 }
89
90 //---------------------------------------------- Update dialog controls ------------------------------------
91
92 int
93 SetCurrentComboSelection (Option *opt)
94 {
95     int j;
96     if(!opt->textValue) opt->value = *(int*)opt->target; /* numeric */else {
97         for(j=0; opt->choice[j]; j++) // look up actual value in list of possible values, to get selection nr
98             if(*(char**)opt->target && !strcmp(*(char**)opt->target, ((char**)opt->textValue)[j])) break;
99         opt->value = j + (opt->choice[j] == NULL);
100     }
101     return opt->value;
102 }
103
104 void
105 GenericUpdate (Option *opts, int selected)
106 {
107     int i;
108     char buf[MSG_SIZ];
109
110     for(i=0; ; i++)
111       {
112         if(selected >= 0) { if(i < selected) continue; else if(i > selected) break; }
113         switch(opts[i].type)
114           {
115           case TextBox:
116           case FileName:
117           case PathName:
118             SetWidgetText(&opts[i],  *(char**) opts[i].target, -1);
119             break;
120           case Spin:
121             sprintf(buf, "%d", *(int*) opts[i].target);
122             SetWidgetText(&opts[i], buf, -1);
123             break;
124           case Fractional:
125             sprintf(buf, "%4.2f", *(float*) opts[i].target);
126             SetWidgetText(&opts[i], buf, -1);
127             break;
128           case CheckBox:
129             SetWidgetState(&opts[i],  *(Boolean*) opts[i].target);
130             break;
131           case ComboBox:
132             if(opts[i].min & COMBO_CALLBACK) break;
133             SetCurrentComboSelection(opts+i);
134             // TODO: actually display this (but it is never used that way...)
135             break;
136           case EndMark:
137             return;
138           default:
139             printf("GenericUpdate: unexpected case in switch.\n");
140           case ListBox:
141           case Button:
142           case SaveButton:
143           case Label:
144           case Break:
145             break;
146           }
147       }
148 }
149
150 //------------------------------------------- Read out dialog controls ------------------------------------
151
152 int
153 GenericReadout (Option *opts, int selected)
154 {
155     int i, j, res=1;
156     char *val;
157     char buf[MSG_SIZ], **dest;
158     float x;
159         for(i=0; ; i++) { // send all options that had to be OK-ed to engine
160             if(selected >= 0) { if(i < selected) continue; else if(i > selected) break; }
161             switch(opts[i].type) {
162                 case TextBox:
163                 case FileName:
164                 case PathName:
165                     GetWidgetText(&opts[i], &val);
166                     dest = currentCps ? &(opts[i].textValue) : (char**) opts[i].target;
167                     if(*dest == NULL || strcmp(*dest, val)) {
168                         if(currentCps) {
169                             snprintf(buf, MSG_SIZ,  "option %s=%s\n", opts[i].name, val);
170                             SendToProgram(buf, currentCps);
171                         } else {
172                             if(*dest) free(*dest);
173                             *dest = malloc(strlen(val)+1);
174                         }
175                         safeStrCpy(*dest, val, MSG_SIZ - (*dest - opts[i].name)); // copy text there
176                     }
177                     break;
178                 case Spin:
179                 case Fractional:
180                     GetWidgetText(&opts[i], &val);
181                     x = 0.0; // Initialise because sscanf() will fail if non-numeric text is entered
182                     sscanf(val, "%f", &x);
183                     if(x > opts[i].max) x = opts[i].max;
184                     if(x < opts[i].min) x = opts[i].min;
185                     if(opts[i].type == Fractional)
186                         *(float*) opts[i].target = x; // engines never have float options!
187                     else if(opts[i].value != x) {
188                         opts[i].value = x;
189                         if(currentCps) {
190                             snprintf(buf, MSG_SIZ,  "option %s=%.0f\n", opts[i].name, x);
191                             SendToProgram(buf, currentCps);
192                         } else *(int*) opts[i].target = x;
193                     }
194                     break;
195                 case CheckBox:
196                     j = 0;
197                     GetWidgetState(&opts[i], &j);
198                     if(opts[i].value != j) {
199                         opts[i].value = j;
200                         if(currentCps) {
201                             snprintf(buf, MSG_SIZ,  "option %s=%d\n", opts[i].name, j);
202                             SendToProgram(buf, currentCps);
203                         } else *(Boolean*) opts[i].target = j;
204                     }
205                     break;
206                 case ComboBox:
207                     if(opts[i].min & COMBO_CALLBACK) break;
208                     if(!opts[i].textValue) { *(int*)opts[i].target = values[i]; break; } // numeric
209                     val = ((char**)opts[i].textValue)[values[i]];
210                     if(currentCps) {
211                         if(opts[i].value == values[i]) break; // not changed
212                         opts[i].value = values[i];
213                         snprintf(buf, MSG_SIZ,  "option %s=%s\n", opts[i].name, opts[i].choice[values[i]]);
214                         SendToProgram(buf, currentCps);
215                     } else if(val && (*(char**) opts[i].target == NULL || strcmp(*(char**) opts[i].target, val))) {
216                       if(*(char**) opts[i].target) free(*(char**) opts[i].target);
217                       *(char**) opts[i].target = strdup(val);
218                     }
219                     break;
220                 case EndMark:
221                     if(opts[i].target) // callback for implementing necessary actions on OK (like redraw)
222                         res = ((OKCallback*) opts[i].target)(i);
223                     break;
224             default:
225                 printf("GenericReadout: unexpected case in switch.\n");
226                 case ListBox:
227                 case Button:
228                 case SaveButton:
229                 case Label:
230                 case Break:
231               break;
232             }
233             if(opts[i].type == EndMark) break;
234         }
235         return res;
236 }
237
238 //------------------------------------------- Match Options ------------------------------------------------------
239
240 char *engineName, *engineChoice, *tfName;
241 char *engineList[MAXENGINES] = {" "}, *engineMnemonic[MAXENGINES];
242
243 static void AddToTourney P((int n, int sel));
244 static void CloneTourney P((void));
245 static void ReplaceParticipant P((void));
246 static void UpgradeParticipant P((void));
247
248 static int
249 MatchOK (int n)
250 {
251     ASSIGN(appData.participants, engineName);
252     if(!CreateTourney(tfName) || matchMode) return matchMode || !appData.participants[0];
253     PopDown(TransientDlg); // early popdown to prevent FreezeUI called through MatchEvent from causing XtGrab warning
254     MatchEvent(2); // start tourney
255     return FALSE;  // no double PopDown!
256 }
257
258 static Option matchOptions[] = {
259 { 0,  0,          0, NULL, (void*) &tfName, ".trn", NULL, FileName, N_("Tournament file:") },
260 { 0,  0,          0, NULL, (void*) &appData.roundSync, "", NULL, CheckBox, N_("Sync after round") },
261 { 0, SAME_ROW|LL, 0, NULL, NULL, "", NULL, Label, N_("    (for concurrent playing of a single") },
262 { 0,  0,          0, NULL, (void*) &appData.cycleSync, "", NULL, CheckBox, N_("Sync after cycle") },
263 { 0, SAME_ROW|LL, 0, NULL, NULL, "", NULL, Label, N_("      tourney with multiple XBoards)") },
264 { 0,  LR,       175, NULL, NULL, "", NULL, Label, N_("Tourney participants:") },
265 { 0, SAME_ROW|RR, 175, NULL, NULL, "", NULL, Label, N_("Select Engine:") },
266 { 150, T_VSCRL | T_FILL | T_WRAP,
267                 175, NULL, (void*) &engineName, "", NULL, TextBox, "" },
268 { 150, SAME_ROW|RR,
269                 175, NULL, (void*) engineMnemonic, (char*) &AddToTourney, NULL, ListBox, "" },
270 //{ 0,  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"), -1, -1);
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 int savedIndex;  /* gross that this is global (and even across files...) */
925
926 static int CommentClick P((Option *opt, int n, int x, int y, char *val, int index));
927
928 static int
929 NewComCallback (int n)
930 {
931     ReplaceComment(commentIndex, commentText);
932     return 1;
933 }
934
935 Option commentOptions[] = {
936 { 200, T_VSCRL | T_FILL | T_WRAP | T_TOP, 250, NULL, (void*) &commentText, "", (MemoCallback *) &CommentClick, TextBox, "" },
937 { 0,     0,     50, NULL, (void*) &ClearComment, NULL, NULL, Button, N_("clear") },
938 { 0, SAME_ROW, 100, NULL, (void*) &SaveChanges, NULL, NULL, Button, N_("save changes") },
939 { 0, SAME_ROW,  0,  NULL, (void*) &NewComCallback, "", NULL, EndMark , "" }
940 };
941
942 static int
943 CommentClick (Option *opt, int n, int x, int y, char *val, int index)
944 {
945         if(n != 3) return FALSE; // only button-3 press is of interest
946         ReplaceComment(savedIndex, val);
947         if(savedIndex != currentMove) ToNrEvent(savedIndex);
948         LoadVariation( index, val ); // [HGM] also does the actual moving to it, now
949         return TRUE;
950 }
951
952 static void
953 SaveChanges (int n)
954 {
955     GenericReadout(commentOptions, 0);
956     ReplaceComment(commentIndex, commentText);
957 }
958
959 static void
960 ClearComment (int n)
961 {
962     SetWidgetText(&commentOptions[0], "", CommentDlg);
963 }
964
965 void
966 NewCommentPopup (char *title, char *text, int index)
967 {
968     if(DialogExists(CommentDlg)) { // if already exists, alter title and content
969         SetDialogTitle(CommentDlg, title);
970         SetWidgetText(&commentOptions[0], text, CommentDlg);
971     }
972     if(commentText) free(commentText); commentText = strdup(text);
973     commentIndex = index;
974     MarkMenu("View.Comments", CommentDlg);
975     if(GenericPopUp(commentOptions, title, CommentDlg, BoardWindow, NONMODAL, 1))
976         AddHandler(&commentOptions[0], CommentDlg, 1);
977 }
978
979 void
980 EditCommentPopUp (int index, char *title, char *text)
981 {
982     savedIndex = index;
983     if (text == NULL) text = "";
984     NewCommentPopup(title, text, index);
985 }
986
987 void
988 CommentPopUp (char *title, char *text)
989 {
990     savedIndex = currentMove; // [HGM] vari
991     NewCommentPopup(title, text, currentMove);
992 }
993
994 void
995 CommentPopDown ()
996 {
997     PopDown(CommentDlg);
998 }
999
1000
1001 void
1002 EditCommentProc ()
1003 {
1004     if (PopDown(CommentDlg)) { // popdown succesful
1005 //      MarkMenuItem("Edit.EditComment", False);
1006 //      MarkMenuItem("View.Comments", False);
1007     } else // was not up
1008         EditCommentEvent();
1009 }
1010
1011 //------------------------------------------------------ Edit Tags ----------------------------------
1012
1013 static void changeTags P((int n));
1014 static char *tagsText;
1015
1016 static int
1017 NewTagsCallback (int n)
1018 {
1019     ReplaceTags(tagsText, &gameInfo);
1020     return 1;
1021 }
1022
1023 static Option tagsOptions[] = {
1024 {   0,   0,   0, NULL, NULL, NULL, NULL, Label,  NULL },
1025 { 200, T_VSCRL | T_FILL | T_WRAP | T_TOP, 200, NULL, (void*) &tagsText, "", NULL, TextBox, "" },
1026 {   0,   0, 100, NULL, (void*) &changeTags, NULL, NULL, Button, N_("save changes") },
1027 { 0,SAME_ROW, 0, NULL, (void*) &NewTagsCallback, "", NULL, EndMark , "" }
1028 };
1029
1030 static void
1031 changeTags (int n)
1032 {
1033     GenericReadout(tagsOptions, 1);
1034     if(bookUp) SaveToBook(tagsText); else
1035     ReplaceTags(tagsText, &gameInfo);
1036 }
1037
1038 void
1039 NewTagsPopup (char *text, char *msg)
1040 {
1041     char *title = bookUp ? _("Edit book") : _("Tags");
1042
1043     if(DialogExists(TagsDlg)) { // if already exists, alter title and content
1044         SetWidgetText(&tagsOptions[1], text, TagsDlg);
1045         SetDialogTitle(TagsDlg, title);
1046     }
1047     if(tagsText) free(tagsText); tagsText = strdup(text);
1048     tagsOptions[0].name = msg;
1049     MarkMenu("View.Tags", TagsDlg);
1050     GenericPopUp(tagsOptions, title, TagsDlg, BoardWindow, NONMODAL, 1);
1051 }
1052
1053 void
1054 TagsPopUp (char *tags, char *msg)
1055 {
1056     NewTagsPopup(tags, cmailMsgLoaded ? msg : NULL);
1057 }
1058
1059 void
1060 EditTagsPopUp (char *tags, char **dest)
1061 {   // wrapper to preserve old name used in back-end
1062     NewTagsPopup(tags, NULL);
1063 }
1064
1065 void
1066 TagsPopDown()
1067 {
1068     PopDown(TagsDlg);
1069     bookUp = False;
1070 }
1071
1072 void
1073 EditTagsProc ()
1074 {
1075   if (bookUp || !PopDown(TagsDlg)) EditTagsEvent();
1076 }
1077
1078 //---------------------------------------------- ICS Input Box ----------------------------------
1079
1080 char *icsText;
1081
1082 // [HGM] code borrowed from winboard.c (which should thus go to backend.c!)
1083 #define HISTORY_SIZE 64
1084 static char *history[HISTORY_SIZE];
1085 static int histIn = 0, histP = 0;
1086
1087 static void
1088 SaveInHistory (char *cmd)
1089 {
1090   if (history[histIn] != NULL) {
1091     free(history[histIn]);
1092     history[histIn] = NULL;
1093   }
1094   if (*cmd == NULLCHAR) return;
1095   history[histIn] = StrSave(cmd);
1096   histIn = (histIn + 1) % HISTORY_SIZE;
1097   if (history[histIn] != NULL) {
1098     free(history[histIn]);
1099     history[histIn] = NULL;
1100   }
1101   histP = histIn;
1102 }
1103
1104 static char *
1105 PrevInHistory (char *cmd)
1106 {
1107   int newhp;
1108   if (histP == histIn) {
1109     if (history[histIn] != NULL) free(history[histIn]);
1110     history[histIn] = StrSave(cmd);
1111   }
1112   newhp = (histP - 1 + HISTORY_SIZE) % HISTORY_SIZE;
1113   if (newhp == histIn || history[newhp] == NULL) return NULL;
1114   histP = newhp;
1115   return history[histP];
1116 }
1117
1118 static char *
1119 NextInHistory ()
1120 {
1121   if (histP == histIn) return NULL;
1122   histP = (histP + 1) % HISTORY_SIZE;
1123   return history[histP];   
1124 }
1125 // end of borrowed code
1126
1127 Option boxOptions[] = {
1128 {  30, T_TOP, 400, NULL, (void*) &icsText, "", NULL, TextBox, "" },
1129 {  0,  NO_OK,   0, NULL, NULL, "", NULL, EndMark , "" }
1130 };
1131
1132 void
1133 ICSInputSendText ()
1134 {
1135     char *val;
1136
1137     GetWidgetText(&boxOptions[0], &val);
1138     SaveInHistory(val);
1139     SendMultiLineToICS(val);
1140     SetWidgetText(&boxOptions[0], "", InputBoxDlg);
1141 }
1142
1143 void
1144 IcsKey (int n)
1145 {   // [HGM] input: let up-arrow recall previous line from history
1146     char *val;
1147
1148     if (!shellUp[InputBoxDlg]) return;
1149     switch(n) {
1150       case 0:
1151         ICSInputSendText();
1152         return;
1153       case 1:
1154         GetWidgetText(&boxOptions[0], &val);
1155         val = PrevInHistory(val);
1156         break;
1157       case -1:
1158         val = NextInHistory();
1159     }
1160     SetWidgetText(&boxOptions[0], val ? val : "", InputBoxDlg);
1161 }
1162
1163 static void
1164 PutText (char *text, int pos)
1165 {
1166     char buf[MSG_SIZ], *p;
1167
1168     if(strstr(text, "$add ") == text) {
1169         GetWidgetText(&boxOptions[0], &p);
1170         snprintf(buf, MSG_SIZ, "%s%s", p, text+5); text = buf;
1171         pos += strlen(p) - 5;
1172     }
1173     SetWidgetText(&boxOptions[0], text, TextMenuDlg);
1174     SetInsertPos(&boxOptions[0], pos);
1175     HardSetFocus(&boxOptions[0]);
1176 }
1177
1178 void
1179 ICSInputBoxPopUp ()
1180 {
1181     MarkMenu("View.ICSInputBox", InputBoxDlg);
1182     if(GenericPopUp(boxOptions, _("ICS input box"), InputBoxDlg, BoardWindow, NONMODAL, 0))
1183         AddHandler(&boxOptions[0], InputBoxDlg, 3);
1184     CursorAtEnd(&boxOptions[0]);
1185 }
1186
1187 void
1188 IcsInputBoxProc ()
1189 {
1190     if (!PopDown(InputBoxDlg)) ICSInputBoxPopUp();
1191 }
1192
1193 //--------------------------------------------- Move Type In ------------------------------------------
1194
1195 static int TypeInOK P((int n));
1196
1197 Option typeOptions[] = {
1198 { 30, T_TOP, 400, NULL, (void*) &icsText, "", NULL, TextBox, "" },
1199 { 0,  NO_OK,   0, NULL, (void*) &TypeInOK, "", NULL, EndMark , "" }
1200 };
1201
1202 static int
1203 TypeInOK (int n)
1204 {
1205     TypeInDoneEvent(icsText);
1206     return TRUE;
1207 }
1208
1209 void
1210 PopUpMoveDialog (char firstchar)
1211 {
1212     static char buf[2];
1213     buf[0] = firstchar; ASSIGN(icsText, buf);
1214     if(GenericPopUp(typeOptions, _("Type a move"), TransientDlg, BoardWindow, MODAL, 0))
1215         AddHandler(&typeOptions[0], TransientDlg, 2);
1216     CursorAtEnd(&typeOptions[0]);
1217 }
1218
1219 void
1220 BoxAutoPopUp (char *buf)
1221 {
1222         if(appData.icsActive) { // text typed to board in ICS mode: divert to ICS input box
1223             if(DialogExists(InputBoxDlg)) { // box already exists: append to current contents
1224                 char *p, newText[MSG_SIZ];
1225                 GetWidgetText(&boxOptions[0], &p);
1226                 snprintf(newText, MSG_SIZ, "%s%c", p, *buf);
1227                 SetWidgetText(&boxOptions[0], newText, InputBoxDlg);
1228                 if(shellUp[InputBoxDlg]) HardSetFocus (&boxOptions[0]); //why???
1229             } else icsText = buf; // box did not exist: make sure it pops up with char in it
1230             ICSInputBoxPopUp();
1231         } else PopUpMoveDialog(*buf);
1232 }
1233
1234 //------------------------------------------ Engine Settings ------------------------------------
1235
1236 void
1237 SettingsPopUp (ChessProgramState *cps)
1238 {
1239    if(!cps->nrOptions) { DisplayNote(_("Engine has no options")); return; }
1240    currentCps = cps;
1241    GenericPopUp(cps->option, _("Engine Settings"), TransientDlg, BoardWindow, MODAL, 0);
1242 }
1243
1244 void
1245 FirstSettingsProc ()
1246 {
1247     SettingsPopUp(&first);
1248 }
1249
1250 void
1251 SecondSettingsProc ()
1252 {
1253    if(WaitForEngine(&second, SettingsMenuIfReady)) return;
1254    SettingsPopUp(&second);
1255 }
1256
1257 //----------------------------------------------- Load Engine --------------------------------------
1258
1259 char *engineDir, *engineLine, *nickName, *params;
1260 Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick, secondEng;
1261
1262 static void EngSel P((int n, int sel));
1263 static int InstallOK P((int n));
1264
1265 static Option installOptions[] = {
1266 {   0,LR|T2T, 0, NULL, NULL, NULL, NULL, Label, N_("Select engine from list:") },
1267 { 300,LR|TB,200, NULL, (void*) engineMnemonic, (char*) &EngSel, NULL, ListBox, "" },
1268 { 0,SAME_ROW, 0, NULL, NULL, NULL, NULL, Break, NULL },
1269 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("or specify one below:") },
1270 {   0,  0,    0, NULL, (void*) &nickName, NULL, NULL, TextBox, N_("Nickname (optional):") },
1271 {   0,  0,    0, NULL, (void*) &useNick, NULL, NULL, CheckBox, N_("Use nickname in PGN player tags of engine-engine games") },
1272 {   0,  0,    0, NULL, (void*) &engineDir, NULL, NULL, PathName, N_("Engine Directory:") },
1273 {   0,  0,    0, NULL, (void*) &engineName, NULL, NULL, FileName, N_("Engine Command:") },
1274 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("(Directory will be derived from engine path when empty)") },
1275 {   0,  0,    0, NULL, (void*) &isUCI, NULL, NULL, CheckBox, N_("UCI") },
1276 {   0,  0,    0, NULL, (void*) &v1, NULL, NULL, CheckBox, N_("WB protocol v1 (do not wait for engine features)") },
1277 {   0,  0,    0, NULL, (void*) &hasBook, NULL, NULL, CheckBox, N_("Must not use GUI book") },
1278 {   0,  0,    0, NULL, (void*) &addToList, NULL, NULL, CheckBox, N_("Add this engine to the list") },
1279 {   0,  0,    0, NULL, (void*) &storeVariant, NULL, NULL, CheckBox, N_("Force current variant with this engine") },
1280 {   0,  0,    0, NULL, (void*) &InstallOK, "", NULL, EndMark , "" }
1281 };
1282
1283 static int
1284 InstallOK (int n)
1285 {
1286     if(n && (n = SelectedListBoxItem(&installOptions[1])) > 0) { // called by pressing OK, and engine selected
1287         ASSIGN(engineLine, engineList[n]);
1288     }
1289     PopDown(TransientDlg); // early popdown, to allow FreezeUI to instate grab
1290     if(!secondEng) Load(&first, 0); else Load(&second, 1);
1291     return FALSE; // no double PopDown!
1292 }
1293
1294 static void
1295 EngSel (int n, int sel)
1296 {
1297     int nr;
1298     char buf[MSG_SIZ];
1299     if(sel < 1) buf[0] = NULLCHAR; // back to top level
1300     else if(engineList[sel][0] == '#') safeStrCpy(buf, engineList[sel], MSG_SIZ); // group header, open group
1301     else { // normal line, select engine
1302         ASSIGN(engineLine, engineList[sel]);
1303         InstallOK(0);
1304         return;
1305     }
1306     nr = NamesToList(firstChessProgramNames, engineList, engineMnemonic, buf); // replace list by only the group contents
1307     ASSIGN(engineMnemonic[0], buf);
1308     LoadListBox(&installOptions[1], _("# no engines are installed"), -1, -1);
1309     HighlightWithScroll(&installOptions[1], 0, nr);
1310 }
1311
1312 static void
1313 LoadEngineProc (int engineNr, char *title)
1314 {
1315    isUCI = storeVariant = v1 = useNick = False; addToList = hasBook = True; // defaults
1316    secondEng = engineNr;
1317    if(engineLine)   free(engineLine);   engineLine = strdup("");
1318    if(engineDir)    free(engineDir);    engineDir = strdup(".");
1319    if(nickName)     free(nickName);     nickName = strdup("");
1320    if(params)       free(params);       params = strdup("");
1321    ASSIGN(engineMnemonic[0], "");
1322    NamesToList(firstChessProgramNames, engineList, engineMnemonic, "");
1323    GenericPopUp(installOptions, title, TransientDlg, BoardWindow, MODAL, 0);
1324 }
1325
1326 void
1327 LoadEngine1Proc ()
1328 {
1329     LoadEngineProc (0, _("Load first engine"));
1330 }
1331
1332 void
1333 LoadEngine2Proc ()
1334 {
1335     LoadEngineProc (1, _("Load second engine"));
1336 }
1337
1338 //----------------------------------------------------- Edit Book -----------------------------------------
1339
1340 void
1341 EditBookProc ()
1342 {
1343     EditBookEvent();
1344 }
1345
1346 //--------------------------------------------------- New Shuffle Game ------------------------------
1347
1348 static void SetRandom P((int n));
1349
1350 static int
1351 ShuffleOK (int n)
1352 {
1353     ResetGameEvent();
1354     return 1;
1355 }
1356
1357 static Option shuffleOptions[] = {
1358   {   0,  0,   50, NULL, (void*) &shuffleOpenings, NULL, NULL, CheckBox, N_("shuffle") },
1359   { 0,-1,2000000000, NULL, (void*) &appData.defaultFrcPosition, "", NULL, Spin, N_("Start-position number:") },
1360   {   0,  0,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("randomize") },
1361   {   0,  SAME_ROW,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("pick fixed") },
1362   { 0,SAME_ROW, 0, NULL, (void*) &ShuffleOK, "", NULL, EndMark , "" }
1363 };
1364
1365 static void
1366 SetRandom (int n)
1367 {
1368     int r = n==2 ? -1 : random() & (1<<30)-1;
1369     char buf[MSG_SIZ];
1370     snprintf(buf, MSG_SIZ,  "%d", r);
1371     SetWidgetText(&shuffleOptions[1], buf, TransientDlg);
1372     SetWidgetState(&shuffleOptions[0], True);
1373 }
1374
1375 void
1376 ShuffleMenuProc ()
1377 {
1378     GenericPopUp(shuffleOptions, _("New Shuffle Game"), TransientDlg, BoardWindow, MODAL, 0);
1379 }
1380
1381 //------------------------------------------------------ Time Control -----------------------------------
1382
1383 static int TcOK P((int n));
1384 int tmpMoves, tmpTc, tmpInc, tmpOdds1, tmpOdds2, tcType;
1385
1386 static void SetTcType P((int n));
1387
1388 static char *
1389 Value (int n)
1390 {
1391         static char buf[MSG_SIZ];
1392         snprintf(buf, MSG_SIZ, "%d", n);
1393         return buf;
1394 }
1395
1396 static Option tcOptions[] = {
1397 {   0,  0,    0, NULL, (void*) &SetTcType, NULL, NULL, Button, N_("classical") },
1398 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("incremental") },
1399 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("fixed max") },
1400 {   0,  0,  200, NULL, (void*) &tmpMoves, NULL, NULL, Spin, N_("Moves per session:") },
1401 {   0,  0,10000, NULL, (void*) &tmpTc,    NULL, NULL, Spin, N_("Initial time (min):") },
1402 {   0, 0, 10000, NULL, (void*) &tmpInc,   NULL, NULL, Spin, N_("Increment or max (sec/move):") },
1403 {   0,  0,    0, NULL, NULL, NULL, NULL, Label, N_("Time-Odds factors:") },
1404 {   0,  1, 1000, NULL, (void*) &tmpOdds1, NULL, NULL, Spin, N_("Engine #1") },
1405 {   0,  1, 1000, NULL, (void*) &tmpOdds2, NULL, NULL, Spin, N_("Engine #2 / Human") },
1406 {   0,  0,    0, NULL, (void*) &TcOK, "", NULL, EndMark , "" }
1407 };
1408
1409 static int
1410 TcOK (int n)
1411 {
1412     char *tc;
1413     if(tcType == 0 && tmpMoves <= 0) return 0;
1414     if(tcType == 2 && tmpInc <= 0) return 0;
1415     GetWidgetText(&tcOptions[4], &tc); // get original text, in case it is min:sec
1416     searchTime = 0;
1417     switch(tcType) {
1418       case 0:
1419         if(!ParseTimeControl(tc, -1, tmpMoves)) return 0;
1420         appData.movesPerSession = tmpMoves;
1421         ASSIGN(appData.timeControl, tc);
1422         appData.timeIncrement = -1;
1423         break;
1424       case 1:
1425         if(!ParseTimeControl(tc, tmpInc, 0)) return 0;
1426         ASSIGN(appData.timeControl, tc);
1427         appData.timeIncrement = tmpInc;
1428         break;
1429       case 2:
1430         searchTime = tmpInc;
1431     }
1432     appData.firstTimeOdds = first.timeOdds = tmpOdds1;
1433     appData.secondTimeOdds = second.timeOdds = tmpOdds2;
1434     Reset(True, True);
1435     return 1;
1436 }
1437
1438 static void
1439 SetTcType (int n)
1440 {
1441     switch(tcType = n) {
1442       case 0:
1443         SetWidgetText(&tcOptions[3], Value(tmpMoves), TransientDlg);
1444         SetWidgetText(&tcOptions[4], Value(tmpTc), TransientDlg);
1445         SetWidgetText(&tcOptions[5], _("Unused"), TransientDlg);
1446         break;
1447       case 1:
1448         SetWidgetText(&tcOptions[3], _("Unused"), TransientDlg);
1449         SetWidgetText(&tcOptions[4], Value(tmpTc), TransientDlg);
1450         SetWidgetText(&tcOptions[5], Value(tmpInc), TransientDlg);
1451         break;
1452       case 2:
1453         SetWidgetText(&tcOptions[3], _("Unused"), TransientDlg);
1454         SetWidgetText(&tcOptions[4], _("Unused"), TransientDlg);
1455         SetWidgetText(&tcOptions[5], Value(tmpInc), TransientDlg);
1456     }
1457 }
1458
1459 void
1460 TimeControlProc ()
1461 {
1462    tmpMoves = appData.movesPerSession;
1463    tmpInc = appData.timeIncrement; if(tmpInc < 0) tmpInc = 0;
1464    tmpOdds1 = tmpOdds2 = 1; tcType = 0;
1465    tmpTc = atoi(appData.timeControl);
1466    GenericPopUp(tcOptions, _("Time Control"), TransientDlg, BoardWindow, MODAL, 0);
1467    SetTcType(searchTime ? 2 : appData.timeIncrement < 0 ? 0 : 1);
1468 }
1469
1470 //------------------------------- Ask Question -----------------------------------------
1471
1472 int SendReply P((int n));
1473 char pendingReplyPrefix[MSG_SIZ];
1474 ProcRef pendingReplyPR;
1475 char *answer;
1476
1477 Option askOptions[] = {
1478 { 0, 0, 0, NULL, NULL, NULL, NULL, Label,  NULL },
1479 { 0, 0, 0, NULL, (void*) &answer, "", NULL, TextBox, "" },
1480 { 0, 0, 0, NULL, (void*) &SendReply, "", NULL, EndMark , "" }
1481 };
1482
1483 int
1484 SendReply (int n)
1485 {
1486     char buf[MSG_SIZ];
1487     int err;
1488     char *reply=answer;
1489 //    GetWidgetText(&askOptions[1], &reply);
1490     safeStrCpy(buf, pendingReplyPrefix, sizeof(buf)/sizeof(buf[0]) );
1491     if (*buf) strncat(buf, " ", MSG_SIZ - strlen(buf) - 1);
1492     strncat(buf, reply, MSG_SIZ - strlen(buf) - 1);
1493     strncat(buf, "\n",  MSG_SIZ - strlen(buf) - 1);
1494     OutputToProcess(pendingReplyPR, buf, strlen(buf), &err); // does not go into debug file??? => bug
1495     if (err) DisplayFatalError(_("Error writing to chess program"), err, 0);
1496     return TRUE;
1497 }
1498
1499 void
1500 AskQuestion (char *title, char *question, char *replyPrefix, ProcRef pr)
1501 {
1502     safeStrCpy(pendingReplyPrefix, replyPrefix, sizeof(pendingReplyPrefix)/sizeof(pendingReplyPrefix[0]) );
1503     pendingReplyPR = pr;
1504     ASSIGN(answer, "");
1505     askOptions[0].name = question;
1506     if(GenericPopUp(askOptions, title, AskDlg, BoardWindow, MODAL, 0))
1507         AddHandler(&askOptions[1], AskDlg, 2);
1508 }
1509
1510 //---------------------------- Promotion Popup --------------------------------------
1511
1512 static int count;
1513
1514 static void PromoPick P((int n));
1515
1516 static Option promoOptions[] = {
1517 {   0,         0,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1518 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1519 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1520 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1521 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1522 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1523 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1524 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1525 {   0, SAME_ROW | NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
1526 };
1527
1528 static void
1529 PromoPick (int n)
1530 {
1531     int promoChar = promoOptions[n+count].value;
1532
1533     PopDown(PromoDlg);
1534
1535     if (promoChar == 0) fromX = -1;
1536     if (fromX == -1) return;
1537
1538     if (! promoChar) {
1539         fromX = fromY = -1;
1540         ClearHighlights();
1541         return;
1542     }
1543     UserMoveEvent(fromX, fromY, toX, toY, promoChar);
1544
1545     if (!appData.highlightLastMove || gotPremove) ClearHighlights();
1546     if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
1547     fromX = fromY = -1;
1548 }
1549
1550 static void
1551 SetPromo (char *name, int nr, char promoChar)
1552 {
1553     ASSIGN(promoOptions[nr].name, name);
1554     promoOptions[nr].value = promoChar;
1555     promoOptions[nr].min = SAME_ROW;
1556 }
1557
1558 void
1559 PromotionPopUp ()
1560 { // choice depends on variant: prepare dialog acordingly
1561   count = 8;
1562   SetPromo(_("Cancel"), --count, 0); // Beware: GenericPopUp cannot handle user buttons named "cancel" (lowe case)!
1563   if(gameInfo.variant != VariantShogi) {
1564     if (!appData.testLegality || gameInfo.variant == VariantSuicide ||
1565         gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove) ||
1566         gameInfo.variant == VariantGiveaway) {
1567       SetPromo(_("King"), --count, 'k');
1568     }
1569     if(gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove)) {
1570       SetPromo(_("Captain"), --count, 'c');
1571       SetPromo(_("Lieutenant"), --count, 'l');
1572       SetPromo(_("General"), --count, 'g');
1573       SetPromo(_("Warlord"), --count, 'w');
1574     } else {
1575       SetPromo(_("Knight"), --count, 'n');
1576       SetPromo(_("Bishop"), --count, 'b');
1577       SetPromo(_("Rook"), --count, 'r');
1578       if(gameInfo.variant == VariantCapablanca ||
1579          gameInfo.variant == VariantGothic ||
1580          gameInfo.variant == VariantCapaRandom) {
1581         SetPromo(_("Archbishop"), --count, 'a');
1582         SetPromo(_("Chancellor"), --count, 'c');
1583       }
1584       SetPromo(_("Queen"), --count, 'q');
1585     }
1586   } else // [HGM] shogi
1587   {
1588       SetPromo(_("Defer"), --count, '=');
1589       SetPromo(_("Promote"), --count, '+');
1590   }
1591   promoOptions[count].min = 0;
1592   GenericPopUp(promoOptions + count, "Promotion", PromoDlg, BoardWindow, NONMODAL, 0);
1593 }
1594
1595 //---------------------------- Chat Windows ----------------------------------------------
1596
1597 static char *line, *memo, *partner, *texts[MAX_CHAT], dirty[MAX_CHAT];
1598 static int activePartner;
1599
1600 void ChatSwitch P((int n));
1601 int  ChatOK P((int n));
1602
1603 Option chatOptions[] = {
1604 { 0,   T_TOP,    100, NULL, (void*) &partner, NULL, NULL, TextBox, N_("Chat partner:") },
1605 { 1, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1606 { 2, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1607 { 3, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1608 { 4, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1609 { 100, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &memo, NULL, NULL, TextBox, "" },
1610 {  0,    0,  510, NULL, (void*) &line, NULL, NULL, TextBox, "" },
1611 { 0, NO_OK|SAME_ROW, 0, NULL, (void*) &ChatOK, NULL, NULL, EndMark , "" }
1612 };
1613
1614 void
1615 OutputChatMessage (int partner, char *mess)
1616 {
1617     char *p = texts[partner];
1618     int len = strlen(mess) + 1;
1619
1620     if(p) len += strlen(p);
1621     texts[partner] = (char*) malloc(len);
1622     snprintf(texts[partner], len, "%s%s", p ? p : "", mess);
1623     FREE(p);
1624     if(partner == activePartner) {
1625         AppendText(&chatOptions[5], mess);
1626         SetInsertPos(&chatOptions[5], len-2);
1627     } else {
1628         SetColor("#FFC000", &chatOptions[partner + (partner < activePartner)]);
1629         dirty[partner] = 1;
1630     }
1631 }
1632
1633 int
1634 ChatOK (int n)
1635 {   // can only be called through <Enter> in chat-partner text-edit, as there is no OK button
1636     char buf[MSG_SIZ];
1637     if(!partner || strcmp(partner, chatPartner[activePartner])) {
1638         safeStrCpy(chatPartner[activePartner], partner, MSG_SIZ);
1639         SetWidgetText(&chatOptions[5], "", -1); // clear text if we alter partner
1640         SetWidgetText(&chatOptions[6], "", ChatDlg); // clear text if we alter partner
1641         HardSetFocus(&chatOptions[6]);
1642     }
1643     if(line[0]) { // something was typed
1644         SetWidgetText(&chatOptions[6], "", ChatDlg);
1645         // from here on it could be back-end
1646         if(line[strlen(line)-1] == '\n') line[strlen(line)-1] = NULLCHAR;
1647         SaveInHistory(line);
1648         if(!strcmp("whispers", chatPartner[activePartner]))
1649               snprintf(buf, MSG_SIZ, "whisper %s\n", line); // WHISPER box uses "whisper" to send
1650         else if(!strcmp("shouts", chatPartner[activePartner]))
1651               snprintf(buf, MSG_SIZ, "shout %s\n", line); // SHOUT box uses "shout" to send
1652         else {
1653             if(!atoi(chatPartner[activePartner])) {
1654                 snprintf(buf, MSG_SIZ, "> %s\n", line); // echo only tells to handle, not channel
1655                 OutputChatMessage(activePartner, buf);
1656                 snprintf(buf, MSG_SIZ, "xtell %s %s\n", chatPartner[activePartner], line);
1657             } else
1658                 snprintf(buf, MSG_SIZ, "tell %s %s\n", chatPartner[activePartner], line);
1659         }
1660         SendToICS(buf);
1661     }
1662     return FALSE; // never pop down
1663 }
1664
1665 void
1666 ChatSwitch (int n)
1667 {
1668     int i, j;
1669     if(n <= activePartner) n--;
1670     activePartner = n;
1671     if(!texts[n]) texts[n] = strdup("");
1672     dirty[n] = 0;
1673     SetWidgetText(&chatOptions[5], texts[n], ChatDlg);
1674     SetInsertPos(&chatOptions[5], strlen(texts[n]));
1675     SetWidgetText(&chatOptions[0], chatPartner[n], ChatDlg);
1676     for(i=j=0; i<MAX_CHAT; i++) {
1677         if(i == activePartner) continue;
1678         SetWidgetLabel(&chatOptions[++j], chatPartner[i]);
1679         SetColor(dirty[i] ? "#FFC000" : "#FFFFFF", &chatOptions[j]);
1680     }
1681     SetWidgetText(&chatOptions[6], "", ChatDlg);
1682     HardSetFocus(&chatOptions[6]);
1683 }
1684
1685 void
1686 ChatProc ()
1687 {
1688     if(GenericPopUp(chatOptions, _("Chat box"), ChatDlg, BoardWindow, NONMODAL, 0))
1689         AddHandler(&chatOptions[0], ChatDlg, 2), AddHandler(&chatOptions[6], ChatDlg, 2); // treats return as OK
1690     MarkMenu("View.OpenChatWindow", ChatDlg);
1691 }
1692
1693 //--------------------------------- Game-List options dialog ------------------------------------------
1694
1695 char *strings[LPUSERGLT_SIZE];
1696 int stringPtr;
1697
1698 void
1699 GLT_ClearList ()
1700 {
1701     strings[0] = NULL;
1702     stringPtr = 0;
1703 }
1704
1705 void
1706 GLT_AddToList (char *name)
1707 {
1708     strings[stringPtr++] = name;
1709     strings[stringPtr] = NULL;
1710 }
1711
1712 Boolean
1713 GLT_GetFromList (int index, char *name)
1714 {
1715   safeStrCpy(name, strings[index], MSG_SIZ);
1716   return TRUE;
1717 }
1718
1719 void
1720 GLT_DeSelectList ()
1721 {
1722 }
1723
1724 static void GLT_Button P((int n));
1725 static int GLT_OK P((int n));
1726
1727 static Option listOptions[] = {
1728 {300, LR|TB, 200, NULL, (void*) strings, "", NULL, ListBox, "" }, // For GTK we need to specify a height, as default would just show 3 lines
1729 { 0,    0,     0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("factory") },
1730 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("up") },
1731 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("down") },
1732 { 0, SAME_ROW, 0, NULL, (void*) &GLT_OK, "", NULL, EndMark , "" }
1733 };
1734
1735 static int
1736 GLT_OK (int n)
1737 {
1738     GLT_ParseList();
1739     appData.gameListTags = strdup(lpUserGLT);
1740     return 1;
1741 }
1742
1743 static void
1744 GLT_Button (int n)
1745 {
1746     int index = SelectedListBoxItem (&listOptions[0]);
1747     char *p;
1748     if (index < 0) {
1749         DisplayError(_("No tag selected"), 0);
1750         return;
1751     }
1752     p = strings[index];
1753     if (n == 3) {
1754         if(index >= strlen(GLT_ALL_TAGS)) return;
1755         strings[index] = strings[index+1];
1756         strings[++index] = p;
1757         LoadListBox(&listOptions[0], "?", index, index-1); // only change the two specified entries
1758     } else
1759     if (n == 2) {
1760         if(index == 0) return;
1761         strings[index] = strings[index-1];
1762         strings[--index] = p;
1763         LoadListBox(&listOptions[0], "?", index, index+1);
1764     } else
1765     if (n == 1) {
1766       safeStrCpy(lpUserGLT, GLT_DEFAULT_TAGS, LPUSERGLT_SIZE);
1767       GLT_TagsToList(lpUserGLT);
1768       index = 0;
1769       LoadListBox(&listOptions[0], "?", -1, -1);
1770     }
1771     HighlightListBoxItem(&listOptions[0], index);
1772 }
1773
1774 void
1775 GameListOptionsPopUp (DialogClass parent)
1776 {
1777     safeStrCpy(lpUserGLT, appData.gameListTags, LPUSERGLT_SIZE);
1778     GLT_TagsToList(lpUserGLT);
1779
1780     GenericPopUp(listOptions, _("Game-list options"), TransientDlg, parent, MODAL, 0);
1781 }
1782
1783 void
1784 GameListOptionsProc ()
1785 {
1786     GameListOptionsPopUp(BoardWindow);
1787 }
1788
1789 //----------------------------- Error popup in various uses -----------------------------
1790
1791 /*
1792  * [HGM] Note:
1793  * XBoard has always had some pathologic behavior with multiple simultaneous error popups,
1794  * (which can occur even for modal popups when asynchrounous events, e.g. caused by engine, request a popup),
1795  * and this new implementation reproduces that as well:
1796  * Only the shell of the last instance is remembered in shells[ErrorDlg] (which replaces errorShell),
1797  * so that PopDowns ordered from the code always refer to that instance, and once that is down,
1798  * have no clue as to how to reach the others. For the Delete Window button calling PopDown this
1799  * has now been repaired, as the action routine assigned to it gets the shell passed as argument.
1800  */
1801
1802 int errorUp = False;
1803
1804 void
1805 ErrorPopDown ()
1806 {
1807     if (!errorUp) return;
1808     dialogError = errorUp = False;
1809     PopDown(ErrorDlg); PopDown(FatalDlg); // on explicit request we pop down any error dialog
1810     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
1811 }
1812
1813 static int
1814 ErrorOK (int n)
1815 {
1816     dialogError = errorUp = False;
1817     PopDown(n == 1 ? FatalDlg : ErrorDlg); // kludge: non-modal dialogs have one less (dummy) option
1818     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
1819     return FALSE; // prevent second Popdown !
1820 }
1821
1822 static Option errorOptions[] = {
1823 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // dummy option: will never be displayed
1824 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // textValue field will be set before popup
1825 { 0,NO_CANCEL,0, NULL, (void*) &ErrorOK, "", NULL, EndMark , "" }
1826 };
1827
1828 void
1829 ErrorPopUp (char *title, char *label, int modal)
1830 {
1831     errorUp = True;
1832     errorOptions[1].name = label;
1833     if(dialogError = shellUp[TransientDlg]) 
1834         GenericPopUp(errorOptions+1, title, FatalDlg, TransientDlg, MODAL, 0); // pop up as daughter of the transient dialog
1835     else
1836         GenericPopUp(errorOptions+modal, title, modal ? FatalDlg: ErrorDlg, BoardWindow, modal, 0); // kludge: option start address indicates modality
1837 }
1838
1839 void
1840 DisplayError (String message, int error)
1841 {
1842     char buf[MSG_SIZ];
1843
1844     if (error == 0) {
1845         if (appData.debugMode || appData.matchMode) {
1846             fprintf(stderr, "%s: %s\n", programName, message);
1847         }
1848     } else {
1849         if (appData.debugMode || appData.matchMode) {
1850             fprintf(stderr, "%s: %s: %s\n",
1851                     programName, message, strerror(error));
1852         }
1853         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
1854         message = buf;
1855     }
1856     ErrorPopUp(_("Error"), message, FALSE);
1857 }
1858
1859
1860 void
1861 DisplayMoveError (String message)
1862 {
1863     fromX = fromY = -1;
1864     ClearHighlights();
1865     DrawPosition(FALSE, NULL);
1866     if (appData.debugMode || appData.matchMode) {
1867         fprintf(stderr, "%s: %s\n", programName, message);
1868     }
1869     if (appData.popupMoveErrors) {
1870         ErrorPopUp(_("Error"), message, FALSE);
1871     } else {
1872         DisplayMessage(message, "");
1873     }
1874 }
1875
1876
1877 void
1878 DisplayFatalError (String message, int error, int status)
1879 {
1880     char buf[MSG_SIZ];
1881
1882     errorExitStatus = status;
1883     if (error == 0) {
1884         fprintf(stderr, "%s: %s\n", programName, message);
1885     } else {
1886         fprintf(stderr, "%s: %s: %s\n",
1887                 programName, message, strerror(error));
1888         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
1889         message = buf;
1890     }
1891     if(mainOptions[W_BOARD].handle) {
1892         if (appData.popupExitMessage) {
1893             ErrorPopUp(status ? _("Fatal Error") : _("Exiting"), message, TRUE);
1894         } else {
1895             ExitEvent(status);
1896         }
1897     }
1898 }
1899
1900 void
1901 DisplayInformation (String message)
1902 {
1903     ErrorPopDown();
1904     ErrorPopUp(_("Information"), message, TRUE);
1905 }
1906
1907 void
1908 DisplayNote (String message)
1909 {
1910     ErrorPopDown();
1911     ErrorPopUp(_("Note"), message, FALSE);
1912 }
1913
1914 void
1915 DisplayTitle (char *text)
1916 {
1917     char title[MSG_SIZ];
1918     char icon[MSG_SIZ];
1919
1920     if (text == NULL) text = "";
1921
1922     if(partnerUp) { SetDialogTitle(DummyDlg, text); return; }
1923
1924     if (*text != NULLCHAR) {
1925       safeStrCpy(icon, text, sizeof(icon)/sizeof(icon[0]) );
1926       safeStrCpy(title, text, sizeof(title)/sizeof(title[0]) );
1927     } else if (appData.icsActive) {
1928         snprintf(icon, sizeof(icon), "%s", appData.icsHost);
1929         snprintf(title, sizeof(title), "%s: %s", programName, appData.icsHost);
1930     } else if (appData.cmailGameName[0] != NULLCHAR) {
1931         snprintf(icon, sizeof(icon), "%s", "CMail");
1932         snprintf(title,sizeof(title), "%s: %s", programName, "CMail");
1933 #ifdef GOTHIC
1934     // [HGM] license: This stuff should really be done in back-end, but WinBoard already had a pop-up for it
1935     } else if (gameInfo.variant == VariantGothic) {
1936       safeStrCpy(icon,  programName, sizeof(icon)/sizeof(icon[0]) );
1937       safeStrCpy(title, GOTHIC,     sizeof(title)/sizeof(title[0]) );
1938 #endif
1939 #ifdef FALCON
1940     } else if (gameInfo.variant == VariantFalcon) {
1941       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
1942       safeStrCpy(title, FALCON, sizeof(title)/sizeof(title[0]) );
1943 #endif
1944     } else if (appData.noChessProgram) {
1945       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
1946       safeStrCpy(title, programName, sizeof(title)/sizeof(title[0]) );
1947     } else {
1948       safeStrCpy(icon, first.tidy, sizeof(icon)/sizeof(icon[0]) );
1949         snprintf(title,sizeof(title), "%s: %s", programName, first.tidy);
1950     }
1951     SetWindowTitle(text, title, icon);
1952 }
1953
1954 #define PAUSE_BUTTON "P"
1955 #define PIECE_MENU_SIZE 18
1956 static String pieceMenuStrings[2][PIECE_MENU_SIZE+1] = {
1957     { N_("White"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
1958       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
1959       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
1960       N_("Empty square"), N_("Clear board"), NULL },
1961     { N_("Black"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
1962       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
1963       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
1964       N_("Empty square"), N_("Clear board"), NULL }
1965 };
1966 /* must be in same order as pieceMenuStrings! */
1967 static ChessSquare pieceMenuTranslation[2][PIECE_MENU_SIZE] = {
1968     { WhitePlay, (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
1969         WhiteRook, WhiteQueen, WhiteKing, (ChessSquare) 0, WhiteAlfil,
1970         WhiteCannon, WhiteAngel, WhiteMarshall, (ChessSquare) 0,
1971         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
1972     { BlackPlay, (ChessSquare) 0, BlackPawn, BlackKnight, BlackBishop,
1973         BlackRook, BlackQueen, BlackKing, (ChessSquare) 0, BlackAlfil,
1974         BlackCannon, BlackAngel, BlackMarshall, (ChessSquare) 0,
1975         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
1976 };
1977
1978 #define DROP_MENU_SIZE 6
1979 static String dropMenuStrings[DROP_MENU_SIZE+1] = {
1980     "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"), N_("Queen"), NULL
1981   };
1982 /* must be in same order as dropMenuStrings! */
1983 static ChessSquare dropMenuTranslation[DROP_MENU_SIZE] = {
1984     (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
1985     WhiteRook, WhiteQueen
1986 };
1987
1988 // [HGM] experimental code to pop up window just like the main window, using GenercicPopUp
1989
1990 static Option *Exp P((int n, int x, int y));
1991 void MenuCallback P((int n));
1992 void SizeKludge P((int n));
1993 static Option *LogoW P((int n, int x, int y));
1994 static Option *LogoB P((int n, int x, int y));
1995
1996 static int pmFromX = -1, pmFromY = -1;
1997 void *userLogo;
1998
1999 void
2000 DisplayLogos (Option *w1, Option *w2)
2001 {
2002         void *whiteLogo = first.programLogo, *blackLogo = second.programLogo;
2003         if(appData.autoLogo) {
2004           
2005           switch(gameMode) { // pick logos based on game mode
2006             case IcsObserving:
2007                 whiteLogo = second.programLogo; // ICS logo
2008                 blackLogo = second.programLogo;
2009             default:
2010                 break;
2011             case IcsPlayingWhite:
2012                 if(!appData.zippyPlay) whiteLogo = userLogo;
2013                 blackLogo = second.programLogo; // ICS logo
2014                 break;
2015             case IcsPlayingBlack:
2016                 whiteLogo = second.programLogo; // ICS logo
2017                 blackLogo = appData.zippyPlay ? first.programLogo : userLogo;
2018                 break;
2019             case TwoMachinesPlay:
2020                 if(first.twoMachinesColor[0] == 'b') {
2021                     whiteLogo = second.programLogo;
2022                     blackLogo = first.programLogo;
2023                 }
2024                 break;
2025             case MachinePlaysWhite:
2026                 blackLogo = userLogo;
2027                 break;
2028             case MachinePlaysBlack:
2029                 whiteLogo = userLogo;
2030                 blackLogo = first.programLogo;
2031           }
2032         }
2033         DrawLogo(w1, whiteLogo);
2034         DrawLogo(w2, blackLogo);
2035 }
2036
2037 static void
2038 PMSelect (int n)
2039 {   // user callback for board context menus
2040     if (pmFromX < 0 || pmFromY < 0) return;
2041     if(n == W_DROP) DropMenuEvent(dropMenuTranslation[values[n]], pmFromX, pmFromY);
2042     else EditPositionMenuEvent(pieceMenuTranslation[n - W_MENUW][values[n]], pmFromX, pmFromY);
2043 }
2044
2045 static void
2046 CCB (int n)
2047 {
2048     shiftKey = (ShiftKeys() & 3) != 0;
2049     ClockClick(n == W_BLACK);
2050 }
2051
2052 Option mainOptions[] = { // description of main window in terms of generic dialog creator
2053 { 0, 0xCA, 0, NULL, NULL, "", NULL, BarBegin, "" }, // menu bar
2054   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("File") },
2055   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Edit") },
2056   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("View") },
2057   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Mode") },
2058   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Action") },
2059   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Engine") },
2060   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Options") },
2061   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Help") },
2062 { 0, 0, 0, NULL, (void*)&SizeKludge, "", NULL, BarEnd, "" },
2063 { 0, LR|T2T|BORDER|SAME_ROW, 0, NULL, NULL, "", NULL, Label, "1" }, // optional title in window
2064 { 50,    LL|TT,            100, NULL, (void*) &LogoW, NULL, NULL, -1, "LogoW" }, // white logo
2065 { 12,   L2L|T2T,           200, NULL, (void*) &CCB, NULL, NULL, Label, "White" }, // white clock
2066 { 13,   R2R|T2T|SAME_ROW,  200, NULL, (void*) &CCB, NULL, NULL, Label, "Black" }, // black clock
2067 { 50,    RR|TT|SAME_ROW,   100, NULL, (void*) &LogoB, NULL, NULL, -1, "LogoB" }, // black logo
2068 { 0, LR|T2T|BORDER,        401, NULL, NULL, "", NULL, -1, "2" }, // backup for title in window (if no room for other)
2069 { 0, LR|T2T|BORDER,        270, NULL, NULL, "", NULL, Label, "message" }, // message field
2070 { 0, RR|TT|SAME_ROW,       125, NULL, NULL, "", NULL, BoxBegin, "" }, // (optional) button bar
2071   { 0,    0,     0, NULL, (void*) &ToStartEvent, NULL, NULL, Button, N_("<<") },
2072   { 0, SAME_ROW, 0, NULL, (void*) &BackwardEvent, NULL, NULL, Button, N_("<") },
2073   { 0, SAME_ROW, 0, NULL, (void*) &PauseEvent, NULL, NULL, Button, N_(PAUSE_BUTTON) },
2074   { 0, SAME_ROW, 0, NULL, (void*) &ForwardEvent, NULL, NULL, Button, N_(">") },
2075   { 0, SAME_ROW, 0, NULL, (void*) &ToEndEvent, NULL, NULL, Button, N_(">>") },
2076 { 0, 0, 0, NULL, NULL, "", NULL, BoxEnd, "" },
2077 { 401, LR|TB, 401, NULL, (char*) &Exp, NULL, NULL, Graph, "shadow board" }, // board
2078   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[0], PopUp, "menuW" },
2079   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[1], PopUp, "menuB" },
2080   { -1, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, dropMenuStrings, PopUp, "menuD" },
2081 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2082 };
2083
2084 Option *
2085 LogoW (int n, int x, int y)
2086 {
2087     if(n == 10) DisplayLogos(&mainOptions[W_WHITE-1], NULL);
2088     return NULL;
2089 }
2090
2091 Option *
2092 LogoB (int n, int x, int y)
2093 {
2094     if(n == 10) DisplayLogos(NULL, &mainOptions[W_BLACK+1]);
2095     return NULL;
2096 }
2097
2098 void
2099 SizeKludge (int n)
2100 {   // callback called by GenericPopUp immediately after sizing the menu bar
2101     int width = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2102     int w = width - 44 - mainOptions[n].min;
2103     mainOptions[W_TITLE].max = w; // width left behind menu bar
2104     if(w < 0.4*width) // if no reasonable amount of space for title, force small layout
2105         mainOptions[W_SMALL].type = mainOptions[W_TITLE].type, mainOptions[W_TITLE].type = -1; 
2106 }
2107
2108 void
2109 MenuCallback (int n)
2110 {
2111     MenuProc *proc = (MenuProc *) (((MenuItem*)(mainOptions[n].choice))[values[n]].proc);
2112
2113     if(!proc) RecentEngineEvent(values[n] - firstEngineItem); else (proc)();
2114 }
2115
2116 static Option *
2117 Exp (int n, int x, int y)
2118 {
2119     static int but1, but3, oldW, oldH;
2120     int menuNr = -3, sizing;
2121
2122     if(n == 0) { // motion
2123         if(SeekGraphClick(Press, x, y, 1)) return NULL;
2124         if(but1 && !PromoScroll(x, y)) DragPieceMove(x, y);
2125         if(but3) MovePV(x, y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
2126         return NULL;
2127     }
2128     if(n != 10 && PopDown(PromoDlg)) fromX = fromY = -1; // user starts fiddling with board when promotion dialog is up
2129     shiftKey = ShiftKeys();
2130     controlKey = (shiftKey & 0xC) != 0;
2131     shiftKey = (shiftKey & 3) != 0;
2132     switch(n) {
2133         case  1: LeftClick(Press,   x, y), but1 = 1; break;
2134         case -1: LeftClick(Release, x, y), but1 = 0; break;
2135         case  2: shiftKey = !shiftKey;
2136         case  3: menuNr = RightClick(Press,   x, y, &pmFromX, &pmFromY), but3 = 1; break;
2137         case -2: shiftKey = !shiftKey;
2138         case -3: menuNr = RightClick(Release, x, y, &pmFromX, &pmFromY), but3 = 0; break;
2139         case 10:
2140             sizing = (oldW != x || oldH != y);
2141             oldW = x; oldH = y;
2142             InitDrawingHandle(mainOptions + W_BOARD);
2143             if(sizing) return NULL; // don't redraw while sizing
2144             DrawPosition(True, NULL);
2145         default:
2146             return NULL;
2147     }
2148
2149     switch(menuNr) {
2150       case 0: return &mainOptions[shiftKey ? W_MENUW: W_MENUB];
2151       case 1: SetupDropMenu(); return &mainOptions[W_DROP];
2152       case 2:
2153       case -1: ErrorPopDown();
2154       case -2:
2155       default: break; // -3, so no clicks caught
2156     }
2157     return NULL;
2158 }
2159
2160 Option *
2161 BoardPopUp (int squareSize, int lineGap, void *clockFontThingy)
2162 {
2163     int i, size = BOARD_WIDTH*(squareSize + lineGap) + lineGap, logo = appData.logoSize;
2164     mainOptions[W_WHITE].choice = (char**) clockFontThingy;
2165     mainOptions[W_BLACK].choice = (char**) clockFontThingy;
2166     mainOptions[W_BOARD].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2167     mainOptions[W_BOARD].max = mainOptions[W_SMALL].max = size; // board size
2168     mainOptions[W_SMALL].max = size - 2; // board title (subtract border!)
2169     mainOptions[W_BLACK].max = mainOptions[W_WHITE].max = size/2-3; // clock width
2170     mainOptions[W_MESSG].max = appData.showButtonBar ? size-135 : size-2; // message
2171     mainOptions[W_MENU].max = size-40; // menu bar
2172     mainOptions[W_TITLE].type = appData.titleInWindow ? Label : -1 ;
2173     if(logo && logo <= size/4) { // Activate logos
2174         mainOptions[W_WHITE-1].type = mainOptions[W_BLACK+1].type = Graph;
2175         mainOptions[W_WHITE-1].max  = mainOptions[W_BLACK+1].max  = logo;
2176         mainOptions[W_WHITE-1].value= mainOptions[W_BLACK+1].value= logo/2;
2177         mainOptions[W_WHITE].min  |= SAME_ROW;
2178         mainOptions[W_WHITE].max  = mainOptions[W_BLACK].max  -= logo + 4;
2179         mainOptions[W_WHITE].name = mainOptions[W_BLACK].name = "Double\nHeight";
2180     }
2181     if(!appData.showButtonBar) for(i=W_BUTTON; i<W_BOARD; i++) mainOptions[i].type = -1;
2182     for(i=0; i<8; i++) mainOptions[i+1].choice = (char**) menuBar[i].mi;
2183     AppendEnginesToMenu(appData.recentEngineList);
2184     GenericPopUp(mainOptions, "XBoard", BoardWindow, BoardWindow, NONMODAL, 1);
2185     return mainOptions;
2186 }
2187
2188 static Option *
2189 SlaveExp (int n, int x, int y)
2190 {
2191     if(n == 10) { // expose event
2192         flipView = !flipView; partnerUp = !partnerUp;
2193         DrawPosition(True, NULL); // [HGM] dual: draw other board in other orientation
2194         flipView = !flipView; partnerUp = !partnerUp;
2195     }
2196     return NULL;
2197 }
2198
2199 Option dualOptions[] = { // auxiliary board window
2200 { 0, L2L|T2T,              198, NULL, NULL, NULL, NULL, Label, "White" }, // white clock
2201 { 0, R2R|T2T|SAME_ROW,     198, NULL, NULL, NULL, NULL, Label, "Black" }, // black clock
2202 { 0, LR|T2T|BORDER,        401, NULL, NULL, NULL, NULL, Label, "message" }, // message field
2203 { 401, LR|TT, 401, NULL, (char*) &SlaveExp, NULL, NULL, Graph, "shadow board" }, // board
2204 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2205 };
2206
2207 void
2208 SlavePopUp ()
2209 {
2210     int size = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2211     // copy params from main board
2212     dualOptions[0].choice = mainOptions[W_WHITE].choice;
2213     dualOptions[1].choice = mainOptions[W_BLACK].choice;
2214     dualOptions[3].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2215     dualOptions[3].max = dualOptions[2].max = size; // board width
2216     dualOptions[0].max = dualOptions[1].max = size/2 - 3; // clock width
2217     GenericPopUp(dualOptions, "XBoard", DummyDlg, BoardWindow, NONMODAL, 1);
2218 }
2219
2220 void
2221 DisplayWhiteClock (long timeRemaining, int highlight)
2222 {
2223     if(appData.noGUI) return;
2224     if(twoBoards && partnerUp) {
2225         DisplayTimerLabel(&dualOptions[0], _("White"), timeRemaining, highlight);
2226         return;
2227     }
2228     DisplayTimerLabel(&mainOptions[W_WHITE], _("White"), timeRemaining, highlight);
2229     if(highlight) SetClockIcon(0);
2230 }
2231
2232 void
2233 DisplayBlackClock (long timeRemaining, int highlight)
2234 {
2235     if(appData.noGUI) return;
2236     if(twoBoards && partnerUp) {
2237         DisplayTimerLabel(&dualOptions[1], _("Black"), timeRemaining, highlight);
2238         return;
2239     }
2240     DisplayTimerLabel(&mainOptions[W_BLACK], _("Black"), timeRemaining, highlight);
2241     if(highlight) SetClockIcon(1);
2242 }
2243
2244
2245 //---------------------------------------------
2246
2247 void
2248 DisplayMessage (char *message, char *extMessage)
2249 {
2250   /* display a message in the message widget */
2251
2252   char buf[MSG_SIZ];
2253
2254   if (extMessage)
2255     {
2256       if (*message)
2257         {
2258           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
2259           message = buf;
2260         }
2261       else
2262         {
2263           message = extMessage;
2264         };
2265     };
2266
2267     safeStrCpy(lastMsg, message, MSG_SIZ); // [HGM] make available
2268
2269   /* need to test if messageWidget already exists, since this function
2270      can also be called during the startup, if for example a Xresource
2271      is not set up correctly */
2272   if(mainOptions[W_MESSG].handle)
2273     SetWidgetLabel(&mainOptions[W_MESSG], message);
2274
2275   return;
2276 }
2277
2278 //----------------------------------- File Browser -------------------------------
2279
2280 #ifdef HAVE_DIRENT_H
2281 #include <dirent.h>
2282 #else
2283 #include <sys/dir.h>
2284 #define dirent direct
2285 #endif
2286
2287 #include <sys/stat.h>
2288
2289 #define MAXFILES 1000
2290
2291 static ChessProgramState *savCps;
2292 static FILE **savFP;
2293 static char *fileName, *extFilter, *savMode, **namePtr;
2294 static int folderPtr, filePtr, oldVal, byExtension, extFlag, pageStart, cnt;
2295 static char curDir[MSG_SIZ], title[MSG_SIZ], *folderList[MAXFILES], *fileList[MAXFILES];
2296
2297 static char *FileTypes[] = {
2298 "Chess Games",
2299 "Chess Positions",
2300 "Tournaments",
2301 "Opening Books",
2302 "Sound files",
2303 "Images",
2304 "Settings (*.ini)",
2305 "Log files",
2306 "All files",
2307 NULL,
2308 "PGN",
2309 "Old-Style Games",
2310 "FEN",
2311 "Old-Style Positions",
2312 NULL,
2313 NULL
2314 };
2315
2316 static char *Extensions[] = {
2317 ".pgn .game",
2318 ".fen .epd .pos",
2319 ".trn",
2320 ".bin",
2321 ".wav",
2322 ".xpm",
2323 ".ini",
2324 ".log",
2325 "",
2326 "INVALID",
2327 ".pgn",
2328 ".game",
2329 ".fen",
2330 ".pos",
2331 NULL,
2332 ""
2333 };
2334
2335 void DirSelProc P((int n, int sel));
2336 void FileSelProc P((int n, int sel));
2337 void SetTypeFilter P((int n));
2338 int BrowseOK P((int n));
2339 void Switch P((int n));
2340 void CreateDir P((int n));
2341
2342 Option browseOptions[] = {
2343 {   0,    LR|T2T,      500, NULL, NULL, NULL, NULL, Label, title },
2344 {   0,    L2L|T2T,     250, NULL, NULL, NULL, NULL, Label, N_("Directories:") },
2345 {   0,R2R|T2T|SAME_ROW,100, NULL, NULL, NULL, NULL, Label, N_("Files:") },
2346 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by name") },
2347 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by type") },
2348 { 300,    L2L|TB,      250, NULL, (void*) folderList, (char*) &DirSelProc, NULL, ListBox, "" },
2349 { 300, R2R|TB|SAME_ROW,250, NULL, (void*) fileList, (char*) &FileSelProc, NULL, ListBox, "" },
2350 {   0,       0,        300, NULL, (void*) &fileName, NULL, NULL, TextBox, N_("Filename:") },
2351 {   0,    SAME_ROW,    120, NULL, (void*) &CreateDir, NULL, NULL, Button, N_("New directory") },
2352 {   0, COMBO_CALLBACK, 150, NULL, (void*) &SetTypeFilter, NULL, FileTypes, ComboBox, N_("File type:") },
2353 {   0,    SAME_ROW,      0, NULL, (void*) &BrowseOK, "", NULL, EndMark , "" }
2354 };
2355
2356 int
2357 BrowseOK (int n)
2358 {
2359         if(!fileName[0]) { // it is enough to have a file selected
2360             if(browseOptions[6].textValue) { // kludge: if callback specified we browse for file
2361                 int sel = SelectedListBoxItem(&browseOptions[6]);
2362                 if(sel < 0 || sel >= filePtr) return FALSE;
2363                 ASSIGN(fileName, fileList[sel]);
2364             } else { // we browse for path
2365                 ASSIGN(fileName, curDir); // kludge: without callback we browse for path
2366             }
2367         }
2368         if(!fileName[0]) return FALSE; // refuse OK when no file
2369         if(!savMode[0]) { // browsing for name only (dialog Browse button)
2370                 if(fileName[0] == '/') // We already had a path name
2371                     snprintf(title, MSG_SIZ, "%s", fileName);
2372                 else
2373                     snprintf(title, MSG_SIZ, "%s/%s", curDir, fileName);
2374                 SetWidgetText((Option*) savFP, title, TransientDlg);
2375                 currentCps = savCps; // could return to Engine Settings dialog!
2376                 return TRUE;
2377         }
2378         *savFP = fopen(fileName, savMode);
2379         if(*savFP == NULL) return FALSE; // refuse OK if file not openable
2380         ASSIGN(*namePtr, fileName);
2381         ScheduleDelayedEvent(DelayedLoad, 50);
2382         currentCps = savCps; // not sure this is ever non-null
2383         return TRUE;
2384 }
2385
2386 int
2387 AlphaNumCompare (char *p, char *q)
2388 {
2389     while(*p) {
2390         if(isdigit(*p) && isdigit(*q) && atoi(p) != atoi(q))
2391              return (atoi(p) > atoi(q) ? 1 : -1);
2392         if(*p != *q) break;
2393         p++, q++;
2394     }
2395     if(*p == *q) return 0;
2396     return (*p > *q ? 1 : -1);
2397 }
2398
2399 int
2400 Comp (const void *s, const void *t)
2401 {
2402     char *p = *(char**) s, *q = *(char**) t;
2403     if(extFlag) {
2404         char *h; int r;
2405         while(h = strchr(p, '.')) p = h+1;
2406         if(p == *(char**) s) p = "";
2407         while(h = strchr(q, '.')) q = h+1;
2408         if(q == *(char**) t) q = "";
2409         r = AlphaNumCompare(p, q);
2410         if(r) return r;
2411     }
2412     return AlphaNumCompare( *(char**) s, *(char**) t );
2413 }
2414
2415 void
2416 ListDir (int pathFlag)
2417 {
2418         DIR *dir;
2419         struct dirent *dp;
2420         struct stat statBuf;
2421         static int lastFlag;
2422
2423         if(pathFlag < 0) pathFlag = lastFlag;
2424         lastFlag = pathFlag;
2425         dir = opendir(".");
2426         getcwd(curDir, MSG_SIZ);
2427         snprintf(title, MSG_SIZ, "%s   %s", _("Contents of"), curDir);
2428         folderPtr = filePtr = cnt = 0; // clear listing
2429
2430         while (dp = readdir(dir)) { // pass 1: list foders
2431             char *s = dp->d_name;
2432             if(!stat(s, &statBuf) && S_ISDIR(statBuf.st_mode)) { // stat succeeds and tells us it is directory
2433                 if(s[0] == '.' && strcmp(s, "..")) continue; // suppress hidden, except ".."
2434                 ASSIGN(folderList[folderPtr], s); if(folderPtr < MAXFILES-2) folderPtr++;
2435             } else if(!pathFlag) {
2436                 char *s = dp->d_name, match=0;
2437 //              if(cnt == pageStart) { ASSIGN }
2438                 if(s[0] == '.') continue; // suppress hidden files
2439                 if(extFilter[0]) { // [HGM] filter on extension
2440                     char *p = extFilter, *q;
2441                     do {
2442                         if(q = strchr(p, ' ')) *q = 0;
2443                         if(strstr(s, p)) match++;
2444                         if(q) *q = ' ';
2445                     } while(q && (p = q+1));
2446                     if(!match) continue;
2447                 }
2448                 if(filePtr == MAXFILES-2) continue;
2449                 if(cnt++ < pageStart) continue;
2450                 ASSIGN(fileList[filePtr], s); filePtr++;
2451             }
2452         }
2453         if(filePtr == MAXFILES-2) { ASSIGN(fileList[filePtr], _("\177 next page")); filePtr++; }
2454         FREE(folderList[folderPtr]); folderList[folderPtr] = NULL;
2455         FREE(fileList[filePtr]); fileList[filePtr] = NULL;
2456         closedir(dir);
2457         extFlag = 0;         qsort((void*)folderList, folderPtr, sizeof(char*), &Comp);
2458         extFlag = byExtension; qsort((void*)fileList, filePtr, sizeof(char*), &Comp);
2459 }
2460
2461 void
2462 Refresh (int pathFlag)
2463 {
2464     ListDir(pathFlag); // and make new one
2465     LoadListBox(&browseOptions[5], "", -1, -1);
2466     LoadListBox(&browseOptions[6], "", -1, -1);
2467     SetWidgetLabel(&browseOptions[0], title);
2468 }
2469
2470 void
2471 CreateDir (int n)
2472 {
2473     char *name, *errmsg = "";
2474     GetWidgetText(&browseOptions[n-1], &name);
2475     if(!name[0]) errmsg = _("FIRST TYPE DIRECTORY NAME HERE"); else
2476     if(mkdir(name, 0755)) errmsg = _("TRY ANOTHER NAME");
2477     else {
2478         chdir(name);
2479         Refresh(-1);
2480     }
2481     SetWidgetText(&browseOptions[n-1], errmsg, BrowserDlg);
2482 }
2483
2484 void
2485 Switch (int n)
2486 {
2487     if(byExtension == (n == 4)) return;
2488     extFlag = byExtension = (n == 4);
2489     qsort((void*)fileList, filePtr, sizeof(char*), &Comp);
2490     LoadListBox(&browseOptions[6], "", -1, -1);
2491 }
2492
2493 void
2494 SetTypeFilter (int n)
2495 {
2496     int j = values[n];
2497     if(j == browseOptions[n].value) return; // no change
2498     browseOptions[n].value = j;
2499     SetWidgetLabel(&browseOptions[n], FileTypes[j]);
2500     ASSIGN(extFilter, Extensions[j]);
2501     pageStart = 0;
2502     Refresh(-1); // uses pathflag remembered by ListDir
2503     values[n] = oldVal; // do not disturb combo settings of underlying dialog
2504 }
2505
2506 void
2507 FileSelProc (int n, int sel)
2508 {
2509     if(sel<0) return;
2510     if(sel == MAXFILES-2) { pageStart = cnt; Refresh(-1); return; }
2511     ASSIGN(fileName, fileList[sel]);
2512     if(BrowseOK(0)) PopDown(BrowserDlg);
2513 }
2514
2515 void
2516 DirSelProc (int n, int sel)
2517 {
2518     if(!chdir(folderList[sel])) { // cd succeeded, so we are in new directory now
2519         Refresh(-1);
2520     }
2521 }
2522
2523 void
2524 Browse (DialogClass dlg, char *label, char *proposed, char *ext, Boolean pathFlag, char *mode, char **name, FILE **fp)
2525 {
2526     int j=0;
2527     savFP = fp; savMode = mode, namePtr = name, savCps = currentCps, oldVal = values[9]; // save params, for use in callback
2528     ASSIGN(extFilter, ext);
2529     ASSIGN(fileName, proposed ? proposed : "");
2530     for(j=0; Extensions[j]; j++) // look up actual value in list of possible values, to get selection nr
2531         if(extFilter && !strcmp(extFilter, Extensions[j])) break;
2532     if(Extensions[j] == NULL) { j++; ASSIGN(FileTypes[j], extFilter); }
2533     browseOptions[9].value = j;
2534     browseOptions[6].textValue = (char*) (pathFlag ? NULL : &FileSelProc); // disable file listbox during path browsing
2535     pageStart = 0; ListDir(pathFlag);
2536     currentCps = NULL;
2537     GenericPopUp(browseOptions, label, BrowserDlg, dlg, MODAL, 0);
2538     SetWidgetLabel(&browseOptions[9], FileTypes[j]);
2539 }
2540
2541 static char *openName;
2542 FileProc fileProc;
2543 char *fileOpenMode;
2544 FILE *openFP;
2545
2546 void
2547 DelayedLoad ()
2548 {
2549   (void) (*fileProc)(openFP, 0, openName);
2550 }
2551
2552 void
2553 FileNamePopUp (char *label, char *def, char *filter, FileProc proc, char *openMode)
2554 {
2555     fileProc = proc;            /* I can't see a way not */
2556     fileOpenMode = openMode;    /*   to use globals here */
2557 #ifdef TODO_GTK
2558     {   // [HGM] use file-selector dialog stolen from Ghostview
2559         // int index; // this is not supported yet
2560         Browse(BoardWindow, label, (def[0] ? def : NULL), filter, False, openMode, &openName, &openFP);
2561     }
2562 #else
2563     FileNamePopUpGTK(label, def, filter, proc, False, openMode, &openName, &openFP);
2564 #endif
2565 }
2566
2567