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