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