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