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