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