Implement ChuChess
[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     UserMoveEvent(fromX, fromY, toX, toY, promoChar);
1604
1605     if (!appData.highlightLastMove || gotPremove) ClearHighlights();
1606     if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
1607     fromX = fromY = -1;
1608 }
1609
1610 static void
1611 SetPromo (char *name, int nr, char promoChar)
1612 {
1613     ASSIGN(promoOptions[nr].name, name);
1614     promoOptions[nr].value = promoChar;
1615     promoOptions[nr].min = SAME_ROW;
1616 }
1617
1618 void
1619 PromotionPopUp ()
1620 { // choice depends on variant: prepare dialog acordingly
1621   count = 8;
1622   SetPromo(_("Cancel"), --count, 0); // Beware: GenericPopUp cannot handle user buttons named "cancel" (lowe case)!
1623   if(!IS_SHOGI(gameInfo.variant)) {
1624     if (!appData.testLegality || gameInfo.variant == VariantSuicide ||
1625         gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove) ||
1626         gameInfo.variant == VariantGiveaway) {
1627       SetPromo(_("King"), --count, 'k');
1628     }
1629     if(gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove)) {
1630       SetPromo(_("Captain"), --count, 'c');
1631       SetPromo(_("Lieutenant"), --count, 'l');
1632       SetPromo(_("General"), --count, 'g');
1633       SetPromo(_("Warlord"), --count, 'w');
1634     } else {
1635       SetPromo(_("Knight"), --count, 'n');
1636       SetPromo(_("Bishop"), --count, 'b');
1637       SetPromo(_("Rook"), --count, 'r');
1638       if(gameInfo.variant == VariantCapablanca ||
1639          gameInfo.variant == VariantGothic ||
1640          gameInfo.variant == VariantCapaRandom) {
1641         SetPromo(_("Archbishop"), --count, 'a');
1642         SetPromo(_("Chancellor"), --count, 'c');
1643       }
1644       SetPromo(_("Queen"), --count, 'q');
1645       if(gameInfo.variant == VariantChuChess)
1646         SetPromo(_("Lion"), --count, 'l');
1647     }
1648   } else // [HGM] shogi
1649   {
1650       SetPromo(_("Defer"), --count, '=');
1651       SetPromo(_("Promote"), --count, '+');
1652   }
1653   promoOptions[count].min = 0;
1654   GenericPopUp(promoOptions + count, "Promotion", PromoDlg, BoardWindow, NONMODAL, 0);
1655 }
1656
1657 //---------------------------- Chat Windows ----------------------------------------------
1658
1659 static char *line, *memo, *partner, *texts[MAX_CHAT], dirty[MAX_CHAT];
1660 static int activePartner;
1661
1662 void ChatSwitch P((int n));
1663 int  ChatOK P((int n));
1664
1665 Option chatOptions[] = {
1666 { 0,   T_TOP,    100, NULL, (void*) &partner, NULL, NULL, TextBox, N_("Chat partner:") },
1667 { 1, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1668 { 2, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1669 { 3, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1670 { 4, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1671 { 100, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &memo, NULL, NULL, TextBox, "" },
1672 {  0,    0,  510, NULL, (void*) &line, NULL, NULL, TextBox, "" },
1673 { 0, NO_OK|SAME_ROW, 0, NULL, (void*) &ChatOK, NULL, NULL, EndMark , "" }
1674 };
1675
1676 void
1677 OutputChatMessage (int partner, char *mess)
1678 {
1679     char *p = texts[partner];
1680     int len = strlen(mess) + 1;
1681
1682     if(p) len += strlen(p);
1683     texts[partner] = (char*) malloc(len);
1684     snprintf(texts[partner], len, "%s%s", p ? p : "", mess);
1685     FREE(p);
1686     if(partner == activePartner) {
1687         AppendText(&chatOptions[5], mess);
1688         SetInsertPos(&chatOptions[5], len-2);
1689     } else {
1690         SetColor("#FFC000", &chatOptions[partner + (partner < activePartner)]);
1691         dirty[partner] = 1;
1692     }
1693 }
1694
1695 int
1696 ChatOK (int n)
1697 {   // can only be called through <Enter> in chat-partner text-edit, as there is no OK button
1698     char buf[MSG_SIZ];
1699
1700     if(!partner || strcmp(partner, chatPartner[activePartner])) {
1701         safeStrCpy(chatPartner[activePartner], partner, MSG_SIZ);
1702         SetWidgetText(&chatOptions[5], "", -1); // clear text if we alter partner
1703         SetWidgetText(&chatOptions[6], "", ChatDlg); // clear text if we alter partner
1704         HardSetFocus(&chatOptions[6]);
1705     }
1706     if(line[0]) { // something was typed
1707         SetWidgetText(&chatOptions[6], "", ChatDlg);
1708         // from here on it could be back-end
1709         if(line[strlen(line)-1] == '\n') line[strlen(line)-1] = NULLCHAR;
1710         SaveInHistory(line);
1711         if(!strcmp("whispers", chatPartner[activePartner]))
1712               snprintf(buf, MSG_SIZ, "whisper %s\n", line); // WHISPER box uses "whisper" to send
1713         else if(!strcmp("shouts", chatPartner[activePartner]))
1714               snprintf(buf, MSG_SIZ, "shout %s\n", line); // SHOUT box uses "shout" to send
1715         else {
1716             if(!atoi(chatPartner[activePartner])) {
1717                 snprintf(buf, MSG_SIZ, "> %s\n", line); // echo only tells to handle, not channel
1718                 OutputChatMessage(activePartner, buf);
1719                 snprintf(buf, MSG_SIZ, "xtell %s %s\n", chatPartner[activePartner], line);
1720             } else
1721                 snprintf(buf, MSG_SIZ, "tell %s %s\n", chatPartner[activePartner], line);
1722         }
1723         SendToICS(buf);
1724     }
1725     return FALSE; // never pop down
1726 }
1727
1728 void
1729 ChatSwitch (int n)
1730 {
1731     int i, j;
1732     if(n <= activePartner) n--;
1733     activePartner = n;
1734     if(!texts[n]) texts[n] = strdup("");
1735     dirty[n] = 0;
1736     SetWidgetText(&chatOptions[5], texts[n], ChatDlg);
1737     SetInsertPos(&chatOptions[5], strlen(texts[n]));
1738     SetWidgetText(&chatOptions[0], chatPartner[n], ChatDlg);
1739     for(i=j=0; i<MAX_CHAT; i++) {
1740         if(i == activePartner) continue;
1741         SetWidgetLabel(&chatOptions[++j], chatPartner[i]);
1742         SetColor(dirty[i] ? "#FFC000" : "#FFFFFF", &chatOptions[j]);
1743     }
1744     SetWidgetText(&chatOptions[6], "", ChatDlg);
1745     HardSetFocus(&chatOptions[6]);
1746 }
1747
1748 void
1749 ChatProc ()
1750 {
1751     if(GenericPopUp(chatOptions, _("Chat box"), ChatDlg, BoardWindow, NONMODAL, appData.topLevel))
1752         AddHandler(&chatOptions[0], ChatDlg, 2), AddHandler(&chatOptions[6], ChatDlg, 2); // treats return as OK
1753     MarkMenu("View.OpenChatWindow", ChatDlg);
1754 }
1755
1756 //--------------------------------- Game-List options dialog ------------------------------------------
1757
1758 char *strings[LPUSERGLT_SIZE];
1759 int stringPtr;
1760
1761 void
1762 GLT_ClearList ()
1763 {
1764     strings[0] = NULL;
1765     stringPtr = 0;
1766 }
1767
1768 void
1769 GLT_AddToList (char *name)
1770 {
1771     strings[stringPtr++] = name;
1772     strings[stringPtr] = NULL;
1773 }
1774
1775 Boolean
1776 GLT_GetFromList (int index, char *name)
1777 {
1778   safeStrCpy(name, strings[index], MSG_SIZ);
1779   return TRUE;
1780 }
1781
1782 void
1783 GLT_DeSelectList ()
1784 {
1785 }
1786
1787 static void GLT_Button P((int n));
1788 static int GLT_OK P((int n));
1789
1790 static Option listOptions[] = {
1791 {300, LR|TB, 200, NULL, (void*) strings, "", NULL, ListBox, "" }, // For GTK we need to specify a height, as default would just show 3 lines
1792 { 0,    0,     0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("factory") },
1793 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("up") },
1794 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("down") },
1795 { 0, SAME_ROW, 0, NULL, (void*) &GLT_OK, "", NULL, EndMark , "" }
1796 };
1797
1798 static int
1799 GLT_OK (int n)
1800 {
1801     GLT_ParseList();
1802     appData.gameListTags = strdup(lpUserGLT);
1803     return 1;
1804 }
1805
1806 static void
1807 GLT_Button (int n)
1808 {
1809     int index = SelectedListBoxItem (&listOptions[0]);
1810     char *p;
1811     if (index < 0) {
1812         DisplayError(_("No tag selected"), 0);
1813         return;
1814     }
1815     p = strings[index];
1816     if (n == 3) {
1817         if(index >= strlen(GLT_ALL_TAGS)) return;
1818         strings[index] = strings[index+1];
1819         strings[++index] = p;
1820         LoadListBox(&listOptions[0], "?", index, index-1); // only change the two specified entries
1821     } else
1822     if (n == 2) {
1823         if(index == 0) return;
1824         strings[index] = strings[index-1];
1825         strings[--index] = p;
1826         LoadListBox(&listOptions[0], "?", index, index+1);
1827     } else
1828     if (n == 1) {
1829       safeStrCpy(lpUserGLT, GLT_DEFAULT_TAGS, LPUSERGLT_SIZE);
1830       GLT_TagsToList(lpUserGLT);
1831       index = 0;
1832       LoadListBox(&listOptions[0], "?", -1, -1);
1833     }
1834     HighlightListBoxItem(&listOptions[0], index);
1835 }
1836
1837 void
1838 GameListOptionsPopUp (DialogClass parent)
1839 {
1840     safeStrCpy(lpUserGLT, appData.gameListTags, LPUSERGLT_SIZE);
1841     GLT_TagsToList(lpUserGLT);
1842
1843     GenericPopUp(listOptions, _("Game-list options"), TransientDlg, parent, MODAL, 0);
1844 }
1845
1846 void
1847 GameListOptionsProc ()
1848 {
1849     GameListOptionsPopUp(BoardWindow);
1850 }
1851
1852 //----------------------------- Error popup in various uses -----------------------------
1853
1854 /*
1855  * [HGM] Note:
1856  * XBoard has always had some pathologic behavior with multiple simultaneous error popups,
1857  * (which can occur even for modal popups when asynchrounous events, e.g. caused by engine, request a popup),
1858  * and this new implementation reproduces that as well:
1859  * Only the shell of the last instance is remembered in shells[ErrorDlg] (which replaces errorShell),
1860  * so that PopDowns ordered from the code always refer to that instance, and once that is down,
1861  * have no clue as to how to reach the others. For the Delete Window button calling PopDown this
1862  * has now been repaired, as the action routine assigned to it gets the shell passed as argument.
1863  */
1864
1865 int errorUp = False;
1866
1867 void
1868 ErrorPopDown ()
1869 {
1870     if (!errorUp) return;
1871     dialogError = errorUp = False;
1872     PopDown(ErrorDlg); PopDown(FatalDlg); // on explicit request we pop down any error dialog
1873     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
1874 }
1875
1876 static int
1877 ErrorOK (int n)
1878 {
1879     dialogError = errorUp = False;
1880     PopDown(n == 1 ? FatalDlg : ErrorDlg); // kludge: non-modal dialogs have one less (dummy) option
1881     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
1882     return FALSE; // prevent second Popdown !
1883 }
1884
1885 static Option errorOptions[] = {
1886 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // dummy option: will never be displayed
1887 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // textValue field will be set before popup
1888 { 0,NO_CANCEL,0, NULL, (void*) &ErrorOK, "", NULL, EndMark , "" }
1889 };
1890
1891 void
1892 ErrorPopUp (char *title, char *label, int modal)
1893 {
1894     errorUp = True;
1895     errorOptions[1].name = label;
1896     if(dialogError = shellUp[TransientDlg])
1897         GenericPopUp(errorOptions+1, title, FatalDlg, TransientDlg, MODAL, 0); // pop up as daughter of the transient dialog
1898     else
1899         GenericPopUp(errorOptions+modal, title, modal ? FatalDlg: ErrorDlg, BoardWindow, modal, 0); // kludge: option start address indicates modality
1900 }
1901
1902 void
1903 DisplayError (String message, int error)
1904 {
1905     char buf[MSG_SIZ];
1906
1907     if (error == 0) {
1908         if (appData.debugMode || appData.matchMode) {
1909             fprintf(stderr, "%s: %s\n", programName, message);
1910         }
1911     } else {
1912         if (appData.debugMode || appData.matchMode) {
1913             fprintf(stderr, "%s: %s: %s\n",
1914                     programName, message, strerror(error));
1915         }
1916         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
1917         message = buf;
1918     }
1919     ErrorPopUp(_("Error"), message, FALSE);
1920 }
1921
1922
1923 void
1924 DisplayMoveError (String message)
1925 {
1926     fromX = fromY = -1;
1927     ClearHighlights();
1928     DrawPosition(TRUE, NULL); // selective redraw would miss the from-square of the rejected move, displayed empty after drag, but not marked damaged!
1929     if (appData.debugMode || appData.matchMode) {
1930         fprintf(stderr, "%s: %s\n", programName, message);
1931     }
1932     if (appData.popupMoveErrors) {
1933         ErrorPopUp(_("Error"), message, FALSE);
1934     } else {
1935         DisplayMessage(message, "");
1936     }
1937 }
1938
1939
1940 void
1941 DisplayFatalError (String message, int error, int status)
1942 {
1943     char buf[MSG_SIZ];
1944
1945     errorExitStatus = status;
1946     if (error == 0) {
1947         fprintf(stderr, "%s: %s\n", programName, message);
1948     } else {
1949         fprintf(stderr, "%s: %s: %s\n",
1950                 programName, message, strerror(error));
1951         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
1952         message = buf;
1953     }
1954     if(mainOptions[W_BOARD].handle) {
1955         if (appData.popupExitMessage) {
1956             ErrorPopUp(status ? _("Fatal Error") : _("Exiting"), message, TRUE);
1957         } else {
1958             ExitEvent(status);
1959         }
1960     }
1961 }
1962
1963 void
1964 DisplayInformation (String message)
1965 {
1966     ErrorPopDown();
1967     ErrorPopUp(_("Information"), message, TRUE);
1968 }
1969
1970 void
1971 DisplayNote (String message)
1972 {
1973     ErrorPopDown();
1974     ErrorPopUp(_("Note"), message, FALSE);
1975 }
1976
1977 void
1978 DisplayTitle (char *text)
1979 {
1980     char title[MSG_SIZ];
1981     char icon[MSG_SIZ];
1982
1983     if (text == NULL) text = "";
1984
1985     if(partnerUp) { SetDialogTitle(DummyDlg, text); return; }
1986
1987     if (*text != NULLCHAR) {
1988       safeStrCpy(icon, text, sizeof(icon)/sizeof(icon[0]) );
1989       safeStrCpy(title, text, sizeof(title)/sizeof(title[0]) );
1990     } else if (appData.icsActive) {
1991         snprintf(icon, sizeof(icon), "%s", appData.icsHost);
1992         snprintf(title, sizeof(title), "%s: %s", programName, appData.icsHost);
1993     } else if (appData.cmailGameName[0] != NULLCHAR) {
1994         snprintf(icon, sizeof(icon), "%s", "CMail");
1995         snprintf(title,sizeof(title), "%s: %s", programName, "CMail");
1996 #ifdef GOTHIC
1997     // [HGM] license: This stuff should really be done in back-end, but WinBoard already had a pop-up for it
1998     } else if (gameInfo.variant == VariantGothic) {
1999       safeStrCpy(icon,  programName, sizeof(icon)/sizeof(icon[0]) );
2000       safeStrCpy(title, GOTHIC,     sizeof(title)/sizeof(title[0]) );
2001 #endif
2002 #ifdef FALCON
2003     } else if (gameInfo.variant == VariantFalcon) {
2004       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
2005       safeStrCpy(title, FALCON, sizeof(title)/sizeof(title[0]) );
2006 #endif
2007     } else if (appData.noChessProgram) {
2008       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
2009       safeStrCpy(title, programName, sizeof(title)/sizeof(title[0]) );
2010     } else {
2011       safeStrCpy(icon, first.tidy, sizeof(icon)/sizeof(icon[0]) );
2012         snprintf(title,sizeof(title), "%s: %s", programName, first.tidy);
2013     }
2014     SetWindowTitle(text, title, icon);
2015 }
2016
2017 #define PAUSE_BUTTON "P"
2018 #define PIECE_MENU_SIZE 18
2019 static String pieceMenuStrings[2][PIECE_MENU_SIZE+1] = {
2020     { N_("White"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
2021       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
2022       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
2023       N_("Empty square"), N_("Clear board"), NULL },
2024     { N_("Black"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
2025       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
2026       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
2027       N_("Empty square"), N_("Clear board"), NULL }
2028 };
2029 /* must be in same order as pieceMenuStrings! */
2030 static ChessSquare pieceMenuTranslation[2][PIECE_MENU_SIZE] = {
2031     { WhitePlay, (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2032         WhiteRook, WhiteQueen, WhiteKing, (ChessSquare) 0, WhiteAlfil,
2033         WhiteCannon, WhiteAngel, WhiteMarshall, (ChessSquare) 0,
2034         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2035     { BlackPlay, (ChessSquare) 0, BlackPawn, BlackKnight, BlackBishop,
2036         BlackRook, BlackQueen, BlackKing, (ChessSquare) 0, BlackAlfil,
2037         BlackCannon, BlackAngel, BlackMarshall, (ChessSquare) 0,
2038         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2039 };
2040
2041 #define DROP_MENU_SIZE 6
2042 static String dropMenuStrings[DROP_MENU_SIZE+1] = {
2043     "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"), N_("Queen"), NULL
2044   };
2045 /* must be in same order as dropMenuStrings! */
2046 static ChessSquare dropMenuTranslation[DROP_MENU_SIZE] = {
2047     (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2048     WhiteRook, WhiteQueen
2049 };
2050
2051 // [HGM] experimental code to pop up window just like the main window, using GenercicPopUp
2052
2053 static Option *Exp P((int n, int x, int y));
2054 void MenuCallback P((int n));
2055 void SizeKludge P((int n));
2056 static Option *LogoW P((int n, int x, int y));
2057 static Option *LogoB P((int n, int x, int y));
2058
2059 static int pmFromX = -1, pmFromY = -1;
2060 void *userLogo;
2061
2062 void
2063 DisplayLogos (Option *w1, Option *w2)
2064 {
2065         void *whiteLogo = first.programLogo, *blackLogo = second.programLogo;
2066         if(appData.autoLogo) {
2067
2068           switch(gameMode) { // pick logos based on game mode
2069             case IcsObserving:
2070                 whiteLogo = second.programLogo; // ICS logo
2071                 blackLogo = second.programLogo;
2072             default:
2073                 break;
2074             case IcsPlayingWhite:
2075                 if(!appData.zippyPlay) whiteLogo = userLogo;
2076                 blackLogo = second.programLogo; // ICS logo
2077                 break;
2078             case IcsPlayingBlack:
2079                 whiteLogo = second.programLogo; // ICS logo
2080                 blackLogo = appData.zippyPlay ? first.programLogo : userLogo;
2081                 break;
2082             case TwoMachinesPlay:
2083                 if(first.twoMachinesColor[0] == 'b') {
2084                     whiteLogo = second.programLogo;
2085                     blackLogo = first.programLogo;
2086                 }
2087                 break;
2088             case MachinePlaysWhite:
2089                 blackLogo = userLogo;
2090                 break;
2091             case MachinePlaysBlack:
2092                 whiteLogo = userLogo;
2093                 blackLogo = first.programLogo;
2094           }
2095         }
2096         DrawLogo(w1, whiteLogo);
2097         DrawLogo(w2, blackLogo);
2098 }
2099
2100 static void
2101 PMSelect (int n)
2102 {   // user callback for board context menus
2103     if (pmFromX < 0 || pmFromY < 0) return;
2104     if(n == W_DROP) DropMenuEvent(dropMenuTranslation[values[n]], pmFromX, pmFromY);
2105     else EditPositionMenuEvent(pieceMenuTranslation[n - W_MENUW][values[n]], pmFromX, pmFromY);
2106 }
2107
2108 static void
2109 CCB (int n)
2110 {
2111     shiftKey = (ShiftKeys() & 3) != 0;
2112     if(n < 0) { // button != 1
2113         n = -n;
2114         if(shiftKey && (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack)) {
2115             AdjustClock(n == W_BLACK, 1);
2116         }
2117     } else
2118     ClockClick(n == W_BLACK);
2119 }
2120
2121 Option mainOptions[] = { // description of main window in terms of generic dialog creator
2122 { 0, 0xCA, 0, NULL, NULL, "", NULL, BarBegin, "" }, // menu bar
2123   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("File") },
2124   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Edit") },
2125   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("View") },
2126   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Mode") },
2127   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Action") },
2128   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Engine") },
2129   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Options") },
2130   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Help") },
2131 { 0, 0, 0, NULL, (void*)&SizeKludge, "", NULL, BarEnd, "" },
2132 { 0, LR|T2T|BORDER|SAME_ROW, 0, NULL, NULL, "", NULL, Label, "1" }, // optional title in window
2133 { 50,    LL|TT,            100, NULL, (void*) &LogoW, NULL, NULL, -1, "" }, // white logo
2134 { 12,   L2L|T2T,           200, NULL, (void*) &CCB, NULL, NULL, Label, "White" }, // white clock
2135 { 13,   R2R|T2T|SAME_ROW,  200, NULL, (void*) &CCB, NULL, NULL, Label, "Black" }, // black clock
2136 { 50,    RR|TT|SAME_ROW,   100, NULL, (void*) &LogoB, NULL, NULL, -1, "" }, // black logo
2137 { 0, LR|T2T|BORDER,        401, NULL, NULL, "", NULL, -1, "2" }, // backup for title in window (if no room for other)
2138 { 0, LR|T2T|BORDER,        270, NULL, NULL, "", NULL, Label, "message" }, // message field
2139 { 0, RR|TT|SAME_ROW,       125, NULL, NULL, "", NULL, BoxBegin, "" }, // (optional) button bar
2140   { 0,    0,     0, NULL, (void*) &ToStartEvent, NULL, NULL, Button, N_("<<") },
2141   { 0, SAME_ROW, 0, NULL, (void*) &BackwardEvent, NULL, NULL, Button, N_("<") },
2142   { 0, SAME_ROW, 0, NULL, (void*) &PauseEvent, NULL, NULL, Button, N_(PAUSE_BUTTON) },
2143   { 0, SAME_ROW, 0, NULL, (void*) &ForwardEvent, NULL, NULL, Button, N_(">") },
2144   { 0, SAME_ROW, 0, NULL, (void*) &ToEndEvent, NULL, NULL, Button, N_(">>") },
2145 { 0, 0, 0, NULL, NULL, "", NULL, BoxEnd, "" },
2146 { 401, LR|TB, 401, NULL, (char*) &Exp, NULL, NULL, Graph, "shadow board" }, // board
2147   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[0], PopUp, "menuW" },
2148   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[1], PopUp, "menuB" },
2149   { -1, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, dropMenuStrings, PopUp, "menuD" },
2150 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2151 };
2152
2153 Option *
2154 LogoW (int n, int x, int y)
2155 {
2156     if(n == 10) DisplayLogos(&mainOptions[W_WHITE-1], NULL);
2157     return NULL;
2158 }
2159
2160 Option *
2161 LogoB (int n, int x, int y)
2162 {
2163     if(n == 10) DisplayLogos(NULL, &mainOptions[W_BLACK+1]);
2164     return NULL;
2165 }
2166
2167 void
2168 SizeKludge (int n)
2169 {   // callback called by GenericPopUp immediately after sizing the menu bar
2170     int width = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2171     int w = width - 44 - mainOptions[n].min;
2172     mainOptions[W_TITLE].max = w; // width left behind menu bar
2173     if(w < 0.4*width) // if no reasonable amount of space for title, force small layout
2174         mainOptions[W_SMALL].type = mainOptions[W_TITLE].type, mainOptions[W_TITLE].type = -1;
2175 }
2176
2177 void
2178 MenuCallback (int n)
2179 {
2180     MenuProc *proc = (MenuProc *) (((MenuItem*)(mainOptions[n].choice))[values[n]].proc);
2181
2182     if(!proc) RecentEngineEvent(values[n] - firstEngineItem); else (proc)();
2183 }
2184
2185 static Option *
2186 Exp (int n, int x, int y)
2187 {
2188     static int but1, but3, oldW, oldH;
2189     int menuNr = -3, sizing, f, r;
2190
2191     if(n == 0) { // motion
2192         if(SeekGraphClick(Press, x, y, 1)) return NULL;
2193         if((but1 || dragging == 2) && !PromoScroll(x, y)) DragPieceMove(x, y);
2194         if(but3) MovePV(x, y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
2195         if(appData.highlightDragging) {
2196             f = EventToSquare(x, BOARD_WIDTH);  if ( flipView && f >= 0) f = BOARD_WIDTH - 1 - f;
2197             r = EventToSquare(y, BOARD_HEIGHT); if (!flipView && r >= 0) r = BOARD_HEIGHT - 1 - r;
2198             HoverEvent(x, y, f, r);
2199         }
2200         return NULL;
2201     }
2202     if(n != 10 && PopDown(PromoDlg)) fromX = fromY = -1; // user starts fiddling with board when promotion dialog is up
2203     shiftKey = ShiftKeys();
2204     controlKey = (shiftKey & 0xC) != 0;
2205     shiftKey = (shiftKey & 3) != 0;
2206     switch(n) {
2207         case  1: LeftClick(Press,   x, y), but1 = 1; break;
2208         case -1: LeftClick(Release, x, y), but1 = 0; break;
2209         case  2: shiftKey = !shiftKey;
2210         case  3: menuNr = RightClick(Press,   x, y, &pmFromX, &pmFromY), but3 = 1; break;
2211         case -2: shiftKey = !shiftKey;
2212         case -3: menuNr = RightClick(Release, x, y, &pmFromX, &pmFromY), but3 = 0; break;
2213         case 10:
2214             sizing = (oldW != x || oldH != y);
2215             oldW = x; oldH = y;
2216             InitDrawingHandle(mainOptions + W_BOARD);
2217             if(sizing) return NULL; // don't redraw while sizing
2218             DrawPosition(True, NULL);
2219         default:
2220             return NULL;
2221     }
2222
2223     switch(menuNr) {
2224       case 0: return &mainOptions[shiftKey ? W_MENUW: W_MENUB];
2225       case 1: SetupDropMenu(); return &mainOptions[W_DROP];
2226       case 2:
2227       case -1: ErrorPopDown();
2228       case -2:
2229       default: break; // -3, so no clicks caught
2230     }
2231     return NULL;
2232 }
2233
2234 Option *
2235 BoardPopUp (int squareSize, int lineGap, void *clockFontThingy)
2236 {
2237     int i, size = BOARD_WIDTH*(squareSize + lineGap) + lineGap, logo = appData.logoSize;
2238     mainOptions[W_WHITE].choice = (char**) clockFontThingy;
2239     mainOptions[W_BLACK].choice = (char**) clockFontThingy;
2240     mainOptions[W_BOARD].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2241     mainOptions[W_BOARD].max = mainOptions[W_SMALL].max = size; // board size
2242     mainOptions[W_SMALL].max = size - 2; // board title (subtract border!)
2243     mainOptions[W_BLACK].max = mainOptions[W_WHITE].max = size/2-3; // clock width
2244     mainOptions[W_MESSG].max = appData.showButtonBar ? size-135 : size-2; // message
2245     mainOptions[W_MENU].max = size-40; // menu bar
2246     mainOptions[W_TITLE].type = appData.titleInWindow ? Label : -1 ;
2247     if(logo && logo <= size/4) { // Activate logos
2248         mainOptions[W_WHITE-1].type = mainOptions[W_BLACK+1].type = Graph;
2249         mainOptions[W_WHITE-1].max  = mainOptions[W_BLACK+1].max  = logo;
2250         mainOptions[W_WHITE-1].value= mainOptions[W_BLACK+1].value= logo/2;
2251         mainOptions[W_WHITE].min  |= SAME_ROW;
2252         mainOptions[W_WHITE].max  = mainOptions[W_BLACK].max  -= logo + 4;
2253         mainOptions[W_WHITE].name = mainOptions[W_BLACK].name = "Double\nHeight";
2254     }
2255     if(!appData.showButtonBar) for(i=W_BUTTON; i<W_BOARD; i++) mainOptions[i].type = -1;
2256     for(i=0; i<8; i++) mainOptions[i+1].choice = (char**) menuBar[i].mi;
2257     AppendEnginesToMenu(appData.recentEngineList);
2258     GenericPopUp(mainOptions, "XBoard", BoardWindow, BoardWindow, NONMODAL, 1); // allways top-level
2259     return mainOptions;
2260 }
2261
2262 static Option *
2263 SlaveExp (int n, int x, int y)
2264 {
2265     if(n == 10) { // expose event
2266         flipView = !flipView; partnerUp = !partnerUp;
2267         DrawPosition(True, NULL); // [HGM] dual: draw other board in other orientation
2268         flipView = !flipView; partnerUp = !partnerUp;
2269     }
2270     return NULL;
2271 }
2272
2273 Option dualOptions[] = { // auxiliary board window
2274 { 0, L2L|T2T,              198, NULL, NULL, NULL, NULL, Label, "White" }, // white clock
2275 { 0, R2R|T2T|SAME_ROW,     198, NULL, NULL, NULL, NULL, Label, "Black" }, // black clock
2276 { 0, LR|T2T|BORDER,        401, NULL, NULL, NULL, NULL, Label, "This feature is experimental" }, // message field
2277 { 401, LR|TT, 401, NULL, (char*) &SlaveExp, NULL, NULL, Graph, "shadow board" }, // board
2278 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2279 };
2280
2281 void
2282 SlavePopUp ()
2283 {
2284     int size = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2285     // copy params from main board
2286     dualOptions[0].choice = mainOptions[W_WHITE].choice;
2287     dualOptions[1].choice = mainOptions[W_BLACK].choice;
2288     dualOptions[3].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2289     dualOptions[3].max = dualOptions[2].max = size; // board width
2290     dualOptions[0].max = dualOptions[1].max = size/2 - 3; // clock width
2291     GenericPopUp(dualOptions, "XBoard", DummyDlg, BoardWindow, NONMODAL, appData.topLevel);
2292     SlaveResize(dualOptions+3);
2293 }
2294
2295 void
2296 DisplayWhiteClock (long timeRemaining, int highlight)
2297 {
2298     if(appData.noGUI) return;
2299     if(twoBoards && partnerUp) {
2300         DisplayTimerLabel(&dualOptions[0], _("White"), timeRemaining, highlight);
2301         return;
2302     }
2303     DisplayTimerLabel(&mainOptions[W_WHITE], _("White"), timeRemaining, highlight);
2304     if(highlight) SetClockIcon(0);
2305 }
2306
2307 void
2308 DisplayBlackClock (long timeRemaining, int highlight)
2309 {
2310     if(appData.noGUI) return;
2311     if(twoBoards && partnerUp) {
2312         DisplayTimerLabel(&dualOptions[1], _("Black"), timeRemaining, highlight);
2313         return;
2314     }
2315     DisplayTimerLabel(&mainOptions[W_BLACK], _("Black"), timeRemaining, highlight);
2316     if(highlight) SetClockIcon(1);
2317 }
2318
2319
2320 //---------------------------------------------
2321
2322 void
2323 DisplayMessage (char *message, char *extMessage)
2324 {
2325   /* display a message in the message widget */
2326
2327   char buf[MSG_SIZ];
2328
2329   if (extMessage)
2330     {
2331       if (*message)
2332         {
2333           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
2334           message = buf;
2335         }
2336       else
2337         {
2338           message = extMessage;
2339         };
2340     };
2341
2342     safeStrCpy(lastMsg, message, MSG_SIZ); // [HGM] make available
2343
2344   /* need to test if messageWidget already exists, since this function
2345      can also be called during the startup, if for example a Xresource
2346      is not set up correctly */
2347   if(mainOptions[W_MESSG].handle)
2348     SetWidgetLabel(&mainOptions[W_MESSG], message);
2349
2350   return;
2351 }
2352
2353 //----------------------------------- File Browser -------------------------------
2354
2355 #ifdef HAVE_DIRENT_H
2356 #include <dirent.h>
2357 #else
2358 #include <sys/dir.h>
2359 #define dirent direct
2360 #endif
2361
2362 #include <sys/stat.h>
2363
2364 #define MAXFILES 1000
2365
2366 static ChessProgramState *savCps;
2367 static FILE **savFP;
2368 static char *fileName, *extFilter, *savMode, **namePtr;
2369 static int folderPtr, filePtr, oldVal, byExtension, extFlag, pageStart, cnt;
2370 static char curDir[MSG_SIZ], title[MSG_SIZ], *folderList[MAXFILES], *fileList[MAXFILES];
2371
2372 static char *FileTypes[] = {
2373 "Chess Games",
2374 "Chess Positions",
2375 "Tournaments",
2376 "Opening Books",
2377 "Sound files",
2378 "Images",
2379 "Settings (*.ini)",
2380 "Log files",
2381 "All files",
2382 NULL,
2383 "PGN",
2384 "Old-Style Games",
2385 "FEN",
2386 "Old-Style Positions",
2387 NULL,
2388 NULL
2389 };
2390
2391 static char *Extensions[] = {
2392 ".pgn .game",
2393 ".fen .epd .pos",
2394 ".trn",
2395 ".bin",
2396 ".wav",
2397 ".ini",
2398 ".log",
2399 "",
2400 "INVALID",
2401 ".pgn",
2402 ".game",
2403 ".fen",
2404 ".pos",
2405 NULL,
2406 ""
2407 };
2408
2409 void DirSelProc P((int n, int sel));
2410 void FileSelProc P((int n, int sel));
2411 void SetTypeFilter P((int n));
2412 int BrowseOK P((int n));
2413 void Switch P((int n));
2414 void CreateDir P((int n));
2415
2416 Option browseOptions[] = {
2417 {   0,    LR|T2T,      500, NULL, NULL, NULL, NULL, Label, title },
2418 {   0,    L2L|T2T,     250, NULL, NULL, NULL, NULL, Label, N_("Directories:") },
2419 {   0,R2R|T2T|SAME_ROW,100, NULL, NULL, NULL, NULL, Label, N_("Files:") },
2420 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by name") },
2421 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by type") },
2422 { 300,    L2L|TB,      250, NULL, (void*) folderList, (char*) &DirSelProc, NULL, ListBox, "" },
2423 { 300, R2R|TB|SAME_ROW,250, NULL, (void*) fileList, (char*) &FileSelProc, NULL, ListBox, "" },
2424 {   0,       0,        300, NULL, (void*) &fileName, NULL, NULL, TextBox, N_("Filename:") },
2425 {   0,    SAME_ROW,    120, NULL, (void*) &CreateDir, NULL, NULL, Button, N_("New directory") },
2426 {   0, COMBO_CALLBACK, 150, NULL, (void*) &SetTypeFilter, NULL, FileTypes, ComboBox, N_("File type:") },
2427 {   0,    SAME_ROW,      0, NULL, (void*) &BrowseOK, "", NULL, EndMark , "" }
2428 };
2429
2430 int
2431 BrowseOK (int n)
2432 {
2433         if(!fileName[0]) { // it is enough to have a file selected
2434             if(browseOptions[6].textValue) { // kludge: if callback specified we browse for file
2435                 int sel = SelectedListBoxItem(&browseOptions[6]);
2436                 if(sel < 0 || sel >= filePtr) return FALSE;
2437                 ASSIGN(fileName, fileList[sel]);
2438             } else { // we browse for path
2439                 ASSIGN(fileName, curDir); // kludge: without callback we browse for path
2440             }
2441         }
2442         if(!fileName[0]) return FALSE; // refuse OK when no file
2443         if(!savMode[0]) { // browsing for name only (dialog Browse button)
2444                 if(fileName[0] == '/') // We already had a path name
2445                     snprintf(title, MSG_SIZ, "%s", fileName);
2446                 else
2447                     snprintf(title, MSG_SIZ, "%s/%s", curDir, fileName);
2448                 SetWidgetText((Option*) savFP, title, TransientDlg);
2449                 currentCps = savCps; // could return to Engine Settings dialog!
2450                 return TRUE;
2451         }
2452         *savFP = fopen(fileName, savMode);
2453         if(*savFP == NULL) return FALSE; // refuse OK if file not openable
2454         ASSIGN(*namePtr, fileName);
2455         ScheduleDelayedEvent(DelayedLoad, 50);
2456         currentCps = savCps; // not sure this is ever non-null
2457         return TRUE;
2458 }
2459
2460 int
2461 AlphaNumCompare (char *p, char *q)
2462 {
2463     while(*p) {
2464         if(isdigit(*p) && isdigit(*q) && atoi(p) != atoi(q))
2465              return (atoi(p) > atoi(q) ? 1 : -1);
2466         if(*p != *q) break;
2467         p++, q++;
2468     }
2469     if(*p == *q) return 0;
2470     return (*p > *q ? 1 : -1);
2471 }
2472
2473 int
2474 Comp (const void *s, const void *t)
2475 {
2476     char *p = *(char**) s, *q = *(char**) t;
2477     if(extFlag) {
2478         char *h; int r;
2479         while(h = strchr(p, '.')) p = h+1;
2480         if(p == *(char**) s) p = "";
2481         while(h = strchr(q, '.')) q = h+1;
2482         if(q == *(char**) t) q = "";
2483         r = AlphaNumCompare(p, q);
2484         if(r) return r;
2485     }
2486     return AlphaNumCompare( *(char**) s, *(char**) t );
2487 }
2488
2489 void
2490 ListDir (int pathFlag)
2491 {
2492         DIR *dir;
2493         struct dirent *dp;
2494         struct stat statBuf;
2495         static int lastFlag;
2496
2497         if(pathFlag < 0) pathFlag = lastFlag;
2498         lastFlag = pathFlag;
2499         dir = opendir(".");
2500         getcwd(curDir, MSG_SIZ);
2501         snprintf(title, MSG_SIZ, "%s   %s", _("Contents of"), curDir);
2502         folderPtr = filePtr = cnt = 0; // clear listing
2503
2504         while (dp = readdir(dir)) { // pass 1: list foders
2505             char *s = dp->d_name;
2506             if(!stat(s, &statBuf) && S_ISDIR(statBuf.st_mode)) { // stat succeeds and tells us it is directory
2507                 if(s[0] == '.' && strcmp(s, "..")) continue; // suppress hidden, except ".."
2508                 ASSIGN(folderList[folderPtr], s); if(folderPtr < MAXFILES-2) folderPtr++;
2509             } else if(!pathFlag) {
2510                 char *s = dp->d_name, match=0;
2511 //              if(cnt == pageStart) { ASSIGN }
2512                 if(s[0] == '.') continue; // suppress hidden files
2513                 if(extFilter[0]) { // [HGM] filter on extension
2514                     char *p = extFilter, *q;
2515                     do {
2516                         if(q = strchr(p, ' ')) *q = 0;
2517                         if(strstr(s, p)) match++;
2518                         if(q) *q = ' ';
2519                     } while(q && (p = q+1));
2520                     if(!match) continue;
2521                 }
2522                 if(filePtr == MAXFILES-2) continue;
2523                 if(cnt++ < pageStart) continue;
2524                 ASSIGN(fileList[filePtr], s); filePtr++;
2525             }
2526         }
2527         if(filePtr == MAXFILES-2) { ASSIGN(fileList[filePtr], _("  next page")); filePtr++; }
2528         FREE(folderList[folderPtr]); folderList[folderPtr] = NULL;
2529         FREE(fileList[filePtr]); fileList[filePtr] = NULL;
2530         closedir(dir);
2531         extFlag = 0;         qsort((void*)folderList, folderPtr, sizeof(char*), &Comp);
2532         extFlag = byExtension; qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
2533 }
2534
2535 void
2536 Refresh (int pathFlag)
2537 {
2538     ListDir(pathFlag); // and make new one
2539     LoadListBox(&browseOptions[5], "", -1, -1);
2540     LoadListBox(&browseOptions[6], "", -1, -1);
2541     SetWidgetLabel(&browseOptions[0], title);
2542 }
2543
2544 void
2545 CreateDir (int n)
2546 {
2547     char *name, *errmsg = "";
2548     GetWidgetText(&browseOptions[n-1], &name);
2549     if(!name[0]) errmsg = _("FIRST TYPE DIRECTORY NAME HERE"); else
2550     if(mkdir(name, 0755)) errmsg = _("TRY ANOTHER NAME");
2551     else {
2552         chdir(name);
2553         Refresh(-1);
2554     }
2555     SetWidgetText(&browseOptions[n-1], errmsg, BrowserDlg);
2556 }
2557
2558 void
2559 Switch (int n)
2560 {
2561     if(byExtension == (n == 4)) return;
2562     extFlag = byExtension = (n == 4);
2563     qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
2564     LoadListBox(&browseOptions[6], "", -1, -1);
2565 }
2566
2567 void
2568 SetTypeFilter (int n)
2569 {
2570     int j = values[n];
2571     if(j == browseOptions[n].value) return; // no change
2572     browseOptions[n].value = j;
2573     SetWidgetLabel(&browseOptions[n], FileTypes[j]);
2574     ASSIGN(extFilter, Extensions[j]);
2575     pageStart = 0;
2576     Refresh(-1); // uses pathflag remembered by ListDir
2577     values[n] = oldVal; // do not disturb combo settings of underlying dialog
2578 }
2579
2580 void
2581 FileSelProc (int n, int sel)
2582 {
2583     if(sel < 0 || fileList[sel] == NULL) return;
2584     if(sel == MAXFILES-2) { pageStart = cnt; Refresh(-1); return; }
2585     ASSIGN(fileName, fileList[sel]);
2586     if(BrowseOK(0)) PopDown(BrowserDlg);
2587 }
2588
2589 void
2590 DirSelProc (int n, int sel)
2591 {
2592     if(!chdir(folderList[sel])) { // cd succeeded, so we are in new directory now
2593         Refresh(-1);
2594     }
2595 }
2596
2597 void
2598 Browse (DialogClass dlg, char *label, char *proposed, char *ext, Boolean pathFlag, char *mode, char **name, FILE **fp)
2599 {
2600     int j=0;
2601     savFP = fp; savMode = mode, namePtr = name, savCps = currentCps, oldVal = values[9]; // save params, for use in callback
2602     ASSIGN(extFilter, ext);
2603     ASSIGN(fileName, proposed ? proposed : "");
2604     for(j=0; Extensions[j]; j++) // look up actual value in list of possible values, to get selection nr
2605         if(extFilter && !strcmp(extFilter, Extensions[j])) break;
2606     if(Extensions[j] == NULL) { j++; ASSIGN(FileTypes[j], extFilter); }
2607     browseOptions[9].value = j;
2608     browseOptions[6].textValue = (char*) (pathFlag ? NULL : &FileSelProc); // disable file listbox during path browsing
2609     pageStart = 0; ListDir(pathFlag);
2610     currentCps = NULL;
2611     GenericPopUp(browseOptions, label, BrowserDlg, dlg, MODAL, 0);
2612     SetWidgetLabel(&browseOptions[9], FileTypes[j]);
2613 }
2614
2615 static char *openName;
2616 FileProc fileProc;
2617 char *fileOpenMode;
2618 FILE *openFP;
2619
2620 void
2621 DelayedLoad ()
2622 {
2623   (void) (*fileProc)(openFP, 0, openName);
2624 }
2625
2626 void
2627 FileNamePopUp (char *label, char *def, char *filter, FileProc proc, char *openMode)
2628 {
2629     fileProc = proc;            /* I can't see a way not */
2630     fileOpenMode = openMode;    /*   to use globals here */
2631     FileNamePopUpWrapper(label, def, filter, proc, False, openMode, &openName, &openFP);
2632 }
2633
2634 void
2635 ActivateTheme (int col)
2636 {
2637 }
2638
2639 char *
2640 Col2Text (int n)
2641 {
2642     return NULL;
2643 }