Add Option type 'Skip'
[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;
1113
1114 static int
1115 NewTagsCallback (int n)
1116 {
1117     if(!bookUp) ReplaceTags(tagsText, &gameInfo);
1118     return 1;
1119 }
1120
1121 static Option tagsOptions[] = {
1122 {   0,   0,   0, NULL, NULL, NULL, NULL, Label,  NULL },
1123 { 200, T_VSCRL | T_FILL | T_WRAP | T_TOP, 200, NULL, (void*) &tagsText, "", NULL, TextBox, "" },
1124 {   0,   0, 100, NULL, (void*) &changeTags, NULL, NULL, Button, N_("save changes") },
1125 { 0,SAME_ROW, 0, NULL, (void*) &NewTagsCallback, "", NULL, EndMark , "" }
1126 };
1127
1128 static void
1129 changeTags (int n)
1130 {
1131     GenericReadout(tagsOptions, 1);
1132     if(bookUp) SaveToBook(tagsText), DisplayBook(currentMove); else
1133     ReplaceTags(tagsText, &gameInfo);
1134 }
1135
1136 void
1137 NewTagsPopup (char *text, char *msg)
1138 {
1139     char *title = bookUp ? _("Edit book") : _("Tags");
1140
1141     if(DialogExists(TagsDlg)) { // if already exists, alter title and content
1142         SetWidgetText(&tagsOptions[1], text, TagsDlg);
1143         SetDialogTitle(TagsDlg, title);
1144     }
1145     if(tagsText) free(tagsText); tagsText = strdup(text);
1146     tagsOptions[0].name = msg;
1147     MarkMenu("View.Tags", TagsDlg);
1148     GenericPopUp(tagsOptions, title, TagsDlg, BoardWindow, NONMODAL, appData.topLevel);
1149 }
1150
1151 void
1152 TagsPopUp (char *tags, char *msg)
1153 {
1154     NewTagsPopup(tags, cmailMsgLoaded ? msg : NULL);
1155 }
1156
1157 void
1158 EditTagsPopUp (char *tags, char **dest)
1159 {   // wrapper to preserve old name used in back-end
1160     NewTagsPopup(tags, NULL);
1161 }
1162
1163 void
1164 TagsPopDown()
1165 {
1166     PopDown(TagsDlg);
1167     bookUp = False;
1168 }
1169
1170 void
1171 EditTagsProc ()
1172 {
1173   if (bookUp || !PopDown(TagsDlg)) EditTagsEvent();
1174 }
1175
1176 //---------------------------------------------- ICS Input Box ----------------------------------
1177
1178 char *icsText;
1179
1180 // [HGM] code borrowed from winboard.c (which should thus go to backend.c!)
1181 #define HISTORY_SIZE 64
1182 static char *history[HISTORY_SIZE];
1183 static int histIn = 0, histP = 0;
1184
1185 static void
1186 SaveInHistory (char *cmd)
1187 {
1188   if (history[histIn] != NULL) {
1189     free(history[histIn]);
1190     history[histIn] = NULL;
1191   }
1192   if (*cmd == NULLCHAR) return;
1193   history[histIn] = StrSave(cmd);
1194   histIn = (histIn + 1) % HISTORY_SIZE;
1195   if (history[histIn] != NULL) {
1196     free(history[histIn]);
1197     history[histIn] = NULL;
1198   }
1199   histP = histIn;
1200 }
1201
1202 static char *
1203 PrevInHistory (char *cmd)
1204 {
1205   int newhp;
1206   if (histP == histIn) {
1207     if (history[histIn] != NULL) free(history[histIn]);
1208     history[histIn] = StrSave(cmd);
1209   }
1210   newhp = (histP - 1 + HISTORY_SIZE) % HISTORY_SIZE;
1211   if (newhp == histIn || history[newhp] == NULL) return NULL;
1212   histP = newhp;
1213   return history[histP];
1214 }
1215
1216 static char *
1217 NextInHistory ()
1218 {
1219   if (histP == histIn) return NULL;
1220   histP = (histP + 1) % HISTORY_SIZE;
1221   return history[histP];
1222 }
1223 // end of borrowed code
1224
1225 Option boxOptions[] = {
1226 {  30, T_TOP, 400, NULL, (void*) &icsText, "", NULL, TextBox, "" },
1227 {  0,  NO_OK,   0, NULL, NULL, "", NULL, EndMark , "" }
1228 };
1229
1230 void
1231 ICSInputSendText ()
1232 {
1233     char *val;
1234
1235     GetWidgetText(&boxOptions[0], &val);
1236     SaveInHistory(val);
1237     SendMultiLineToICS(val);
1238     SetWidgetText(&boxOptions[0], "", InputBoxDlg);
1239 }
1240
1241 void
1242 IcsKey (int n)
1243 {   // [HGM] input: let up-arrow recall previous line from history
1244     char *val = NULL; // to suppress spurious warning
1245
1246     if (!shellUp[InputBoxDlg]) return;
1247     switch(n) {
1248       case 0:
1249         ICSInputSendText();
1250         return;
1251       case 1:
1252         GetWidgetText(&boxOptions[0], &val);
1253         val = PrevInHistory(val);
1254         break;
1255       case -1:
1256         val = NextInHistory();
1257     }
1258     SetWidgetText(&boxOptions[0], val = val ? val : "", InputBoxDlg);
1259     SetInsertPos(&boxOptions[0], strlen(val));
1260 }
1261
1262 static void
1263 PutText (char *text, int pos)
1264 {
1265     char buf[MSG_SIZ], *p;
1266
1267     if(strstr(text, "$add ") == text) {
1268         GetWidgetText(&boxOptions[0], &p);
1269         snprintf(buf, MSG_SIZ, "%s%s", p, text+5); text = buf;
1270         pos += strlen(p) - 5;
1271     }
1272     SetWidgetText(&boxOptions[0], text, TextMenuDlg);
1273     SetInsertPos(&boxOptions[0], pos);
1274     HardSetFocus(&boxOptions[0]);
1275 }
1276
1277 void
1278 ICSInputBoxPopUp ()
1279 {
1280     MarkMenu("View.ICSInputBox", InputBoxDlg);
1281     if(GenericPopUp(boxOptions, _("ICS input box"), InputBoxDlg, BoardWindow, NONMODAL, 0))
1282         AddHandler(&boxOptions[0], InputBoxDlg, 3);
1283     CursorAtEnd(&boxOptions[0]);
1284 }
1285
1286 void
1287 IcsInputBoxProc ()
1288 {
1289     if (!PopDown(InputBoxDlg)) ICSInputBoxPopUp();
1290 }
1291
1292 //--------------------------------------------- Move Type In ------------------------------------------
1293
1294 static int TypeInOK P((int n));
1295
1296 Option typeOptions[] = {
1297 { 30, T_TOP, 400, NULL, (void*) &icsText, "", NULL, TextBox, "" },
1298 { 0,  NO_OK,   0, NULL, (void*) &TypeInOK, "", NULL, EndMark , "" }
1299 };
1300
1301 static int
1302 TypeInOK (int n)
1303 {
1304     TypeInDoneEvent(icsText);
1305     return TRUE;
1306 }
1307
1308 void
1309 PopUpMoveDialog (char firstchar)
1310 {
1311     static char buf[2];
1312     buf[0] = firstchar; ASSIGN(icsText, buf);
1313     if(GenericPopUp(typeOptions, _("Type a move"), TransientDlg, BoardWindow, MODAL, 0))
1314         AddHandler(&typeOptions[0], TransientDlg, 2);
1315     CursorAtEnd(&typeOptions[0]);
1316 }
1317
1318 void
1319 BoxAutoPopUp (char *buf)
1320 {
1321         if(!appData.autoBox) return;
1322         if(appData.icsActive) { // text typed to board in ICS mode: divert to ICS input box
1323             if(DialogExists(InputBoxDlg)) { // box already exists: append to current contents
1324                 char *p, newText[MSG_SIZ];
1325                 GetWidgetText(&boxOptions[0], &p);
1326                 snprintf(newText, MSG_SIZ, "%s%c", p, *buf);
1327                 SetWidgetText(&boxOptions[0], newText, InputBoxDlg);
1328                 if(shellUp[InputBoxDlg]) HardSetFocus (&boxOptions[0]); //why???
1329             } else icsText = buf; // box did not exist: make sure it pops up with char in it
1330             ICSInputBoxPopUp();
1331         } else PopUpMoveDialog(*buf);
1332 }
1333
1334 //------------------------------------------ Engine Settings ------------------------------------
1335
1336 void
1337 SettingsPopUp (ChessProgramState *cps)
1338 {
1339    if(!cps->nrOptions) { DisplayNote(_("Engine has no options")); return; }
1340    currentCps = cps;
1341    GenericPopUp(cps->option, _("Engine Settings"), TransientDlg, BoardWindow, MODAL, 0);
1342 }
1343
1344 void
1345 FirstSettingsProc ()
1346 {
1347     SettingsPopUp(&first);
1348 }
1349
1350 void
1351 SecondSettingsProc ()
1352 {
1353    if(WaitForEngine(&second, SettingsMenuIfReady)) return;
1354    SettingsPopUp(&second);
1355 }
1356
1357 //----------------------------------------------- Load Engine --------------------------------------
1358
1359 char *engineDir, *engineLine, *nickName, *params;
1360 Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick, secondEng;
1361
1362 static void EngSel P((int n, int sel));
1363 static int InstallOK P((int n));
1364
1365 static Option installOptions[] = {
1366 {   0,LR|T2T, 0, NULL, NULL, NULL, NULL, Label, N_("Select engine from list:") },
1367 { 300,LR|TB,200, NULL, (void*) engineMnemonic, (char*) &EngSel, NULL, ListBox, "" },
1368 { 0,SAME_ROW, 0, NULL, NULL, NULL, NULL, Break, NULL },
1369 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("or specify one below:") },
1370 {   0,  0,    0, NULL, (void*) &nickName, NULL, NULL, TextBox, N_("Nickname (optional):") },
1371 {   0,  0,    0, NULL, (void*) &useNick, NULL, NULL, CheckBox, N_("Use nickname in PGN player tags of engine-engine games") },
1372 {   0,  0,    0, NULL, (void*) &engineDir, NULL, NULL, PathName, N_("Engine Directory:") },
1373 {   0,  0,    0, NULL, (void*) &engineName, NULL, NULL, FileName, N_("Engine Command:") },
1374 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("(Directory will be derived from engine path when empty)") },
1375 {   0,  0,    0, NULL, (void*) &isUCI, NULL, NULL, CheckBox, N_("UCI") },
1376 {   0,  0,    0, NULL, (void*) &v1, NULL, NULL, CheckBox, N_("WB protocol v1 (do not wait for engine features)") },
1377 {   0,  0,    0, NULL, (void*) &hasBook, NULL, NULL, CheckBox, N_("Must not use GUI book") },
1378 {   0,  0,    0, NULL, (void*) &addToList, NULL, NULL, CheckBox, N_("Add this engine to the list") },
1379 {   0,  0,    0, NULL, (void*) &storeVariant, NULL, NULL, CheckBox, N_("Force current variant with this engine") },
1380 {   0,  0,    0, NULL, (void*) &InstallOK, "", NULL, EndMark , "" }
1381 };
1382
1383 static int
1384 InstallOK (int n)
1385 {
1386     if(n && (n = SelectedListBoxItem(&installOptions[1])) > 0) { // called by pressing OK, and engine selected
1387         ASSIGN(engineLine, engineList[n]);
1388     }
1389     PopDown(TransientDlg); // early popdown, to allow FreezeUI to instate grab
1390     if(!secondEng) Load(&first, 0); else Load(&second, 1);
1391     return FALSE; // no double PopDown!
1392 }
1393
1394 static void
1395 EngSel (int n, int sel)
1396 {
1397     int nr;
1398     char buf[MSG_SIZ];
1399     if(sel < 1) buf[0] = NULLCHAR; // back to top level
1400     else if(engineList[sel][0] == '#') safeStrCpy(buf, engineList[sel], MSG_SIZ); // group header, open group
1401     else { // normal line, select engine
1402         ASSIGN(engineLine, engineList[sel]);
1403         InstallOK(0);
1404         return;
1405     }
1406     nr = NamesToList(firstChessProgramNames, engineList, engineMnemonic, buf); // replace list by only the group contents
1407     ASSIGN(engineMnemonic[0], buf);
1408     LoadListBox(&installOptions[1], _("# no engines are installed"), -1, -1);
1409     HighlightWithScroll(&installOptions[1], 0, nr);
1410 }
1411
1412 static void
1413 LoadEngineProc (int engineNr, char *title)
1414 {
1415    isUCI = storeVariant = v1 = useNick = False; addToList = hasBook = True; // defaults
1416    secondEng = engineNr;
1417    if(engineLine)   free(engineLine);   engineLine = strdup("");
1418    if(engineDir)    free(engineDir);    engineDir = strdup(".");
1419    if(nickName)     free(nickName);     nickName = strdup("");
1420    if(params)       free(params);       params = strdup("");
1421    ASSIGN(engineMnemonic[0], "");
1422    NamesToList(firstChessProgramNames, engineList, engineMnemonic, "");
1423    GenericPopUp(installOptions, title, TransientDlg, BoardWindow, MODAL, 0);
1424 }
1425
1426 void
1427 LoadEngine1Proc ()
1428 {
1429     LoadEngineProc (0, _("Load first engine"));
1430 }
1431
1432 void
1433 LoadEngine2Proc ()
1434 {
1435     LoadEngineProc (1, _("Load second engine"));
1436 }
1437
1438 //----------------------------------------------------- Edit Book -----------------------------------------
1439
1440 void
1441 EditBookProc ()
1442 {
1443     EditBookEvent();
1444 }
1445
1446 //--------------------------------------------------- New Shuffle Game ------------------------------
1447
1448 static void SetRandom P((int n));
1449
1450 static int
1451 ShuffleOK (int n)
1452 {
1453     ResetGameEvent();
1454     return 1;
1455 }
1456
1457 static Option shuffleOptions[] = {
1458   {   0,  0,    0, NULL, (void*) &shuffleOpenings, NULL, NULL, CheckBox, N_("shuffle") },
1459   {   0,  0,    0, NULL, (void*) &appData.fischerCastling, NULL, NULL, CheckBox, N_("Fischer castling") },
1460   { 0,-1,2000000000, NULL, (void*) &appData.defaultFrcPosition, "", NULL, Spin, N_("Start-position number:") },
1461   {   0,  0,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("randomize") },
1462   {   0,  SAME_ROW,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("pick fixed") },
1463   { 0,SAME_ROW, 0, NULL, (void*) &ShuffleOK, "", NULL, EndMark , "" }
1464 };
1465
1466 static void
1467 SetRandom (int n)
1468 {
1469     int r = n==2 ? -1 : random() & (1<<30)-1;
1470     char buf[MSG_SIZ];
1471     snprintf(buf, MSG_SIZ,  "%d", r);
1472     SetWidgetText(&shuffleOptions[1], buf, TransientDlg);
1473     SetWidgetState(&shuffleOptions[0], True);
1474 }
1475
1476 void
1477 ShuffleMenuProc ()
1478 {
1479     GenericPopUp(shuffleOptions, _("New Shuffle Game"), TransientDlg, BoardWindow, MODAL, 0);
1480 }
1481
1482 //------------------------------------------------------ Time Control -----------------------------------
1483
1484 static int TcOK P((int n));
1485 int tmpMoves, tmpTc, tmpInc, tmpOdds1, tmpOdds2, tcType;
1486
1487 static void SetTcType P((int n));
1488
1489 static char *
1490 Value (int n)
1491 {
1492         static char buf[MSG_SIZ];
1493         snprintf(buf, MSG_SIZ, "%d", n);
1494         return buf;
1495 }
1496
1497 static Option tcOptions[] = {
1498 {   0,  0,    0, NULL, (void*) &SetTcType, NULL, NULL, Button, N_("classical") },
1499 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("incremental") },
1500 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("fixed max") },
1501 {   0,  0,  200, NULL, (void*) &tmpMoves, NULL, NULL, Spin, N_("Moves per session:") },
1502 {   0,  0,10000, NULL, (void*) &tmpTc,    NULL, NULL, Spin, N_("Initial time (min):") },
1503 {   0, 0, 10000, NULL, (void*) &tmpInc,   NULL, NULL, Spin, N_("Increment or max (sec/move):") },
1504 {   0,  0,    0, NULL, NULL, NULL, NULL, Label, N_("Time-Odds factors:") },
1505 {   0,  1, 1000, NULL, (void*) &tmpOdds1, NULL, NULL, Spin, N_("Engine #1") },
1506 {   0,  1, 1000, NULL, (void*) &tmpOdds2, NULL, NULL, Spin, N_("Engine #2 / Human") },
1507 {   0,  0,    0, NULL, (void*) &TcOK, "", NULL, EndMark , "" }
1508 };
1509
1510 static int
1511 TcOK (int n)
1512 {
1513     char *tc;
1514     if(tcType == 0 && tmpMoves <= 0) return 0;
1515     if(tcType == 2 && tmpInc <= 0) return 0;
1516     GetWidgetText(&tcOptions[4], &tc); // get original text, in case it is min:sec
1517     searchTime = 0;
1518     switch(tcType) {
1519       case 0:
1520         if(!ParseTimeControl(tc, -1, tmpMoves)) return 0;
1521         appData.movesPerSession = tmpMoves;
1522         ASSIGN(appData.timeControl, tc);
1523         appData.timeIncrement = -1;
1524         break;
1525       case 1:
1526         if(!ParseTimeControl(tc, tmpInc, 0)) return 0;
1527         ASSIGN(appData.timeControl, tc);
1528         appData.timeIncrement = tmpInc;
1529         break;
1530       case 2:
1531         searchTime = tmpInc;
1532     }
1533     appData.firstTimeOdds = first.timeOdds = tmpOdds1;
1534     appData.secondTimeOdds = second.timeOdds = tmpOdds2;
1535     Reset(True, True);
1536     return 1;
1537 }
1538
1539 static void
1540 SetTcType (int n)
1541 {
1542     switch(tcType = n) {
1543       case 0:
1544         SetWidgetText(&tcOptions[3], Value(tmpMoves), TransientDlg);
1545         SetWidgetText(&tcOptions[4], Value(tmpTc), TransientDlg);
1546         SetWidgetText(&tcOptions[5], _("Unused"), TransientDlg);
1547         break;
1548       case 1:
1549         SetWidgetText(&tcOptions[3], _("Unused"), TransientDlg);
1550         SetWidgetText(&tcOptions[4], Value(tmpTc), TransientDlg);
1551         SetWidgetText(&tcOptions[5], Value(tmpInc), TransientDlg);
1552         break;
1553       case 2:
1554         SetWidgetText(&tcOptions[3], _("Unused"), TransientDlg);
1555         SetWidgetText(&tcOptions[4], _("Unused"), TransientDlg);
1556         SetWidgetText(&tcOptions[5], Value(tmpInc), TransientDlg);
1557     }
1558 }
1559
1560 void
1561 TimeControlProc ()
1562 {
1563    tmpMoves = appData.movesPerSession;
1564    tmpInc = appData.timeIncrement; if(tmpInc < 0) tmpInc = 0;
1565    tmpOdds1 = tmpOdds2 = 1; tcType = 0;
1566    tmpTc = atoi(appData.timeControl);
1567    GenericPopUp(tcOptions, _("Time Control"), TransientDlg, BoardWindow, MODAL, 0);
1568    SetTcType(searchTime ? 2 : appData.timeIncrement < 0 ? 0 : 1);
1569 }
1570
1571 //------------------------------- Ask Question -----------------------------------------
1572
1573 int SendReply P((int n));
1574 char pendingReplyPrefix[MSG_SIZ];
1575 ProcRef pendingReplyPR;
1576 char *answer;
1577
1578 Option askOptions[] = {
1579 { 0, 0, 0, NULL, NULL, NULL, NULL, Label,  NULL },
1580 { 0, 0, 0, NULL, (void*) &answer, "", NULL, TextBox, "" },
1581 { 0, 0, 0, NULL, (void*) &SendReply, "", NULL, EndMark , "" }
1582 };
1583
1584 int
1585 SendReply (int n)
1586 {
1587     char buf[MSG_SIZ];
1588     int err;
1589     char *reply=answer;
1590 //    GetWidgetText(&askOptions[1], &reply);
1591     safeStrCpy(buf, pendingReplyPrefix, sizeof(buf)/sizeof(buf[0]) );
1592     if (*buf) strncat(buf, " ", MSG_SIZ - strlen(buf) - 1);
1593     strncat(buf, reply, MSG_SIZ - strlen(buf) - 1);
1594     strncat(buf, "\n",  MSG_SIZ - strlen(buf) - 1);
1595     OutputToProcess(pendingReplyPR, buf, strlen(buf), &err); // does not go into debug file??? => bug
1596     if (err) DisplayFatalError(_("Error writing to chess program"), err, 0);
1597     return TRUE;
1598 }
1599
1600 void
1601 AskQuestion (char *title, char *question, char *replyPrefix, ProcRef pr)
1602 {
1603     safeStrCpy(pendingReplyPrefix, replyPrefix, sizeof(pendingReplyPrefix)/sizeof(pendingReplyPrefix[0]) );
1604     pendingReplyPR = pr;
1605     ASSIGN(answer, "");
1606     askOptions[0].name = question;
1607     if(GenericPopUp(askOptions, title, AskDlg, BoardWindow, MODAL, 0))
1608         AddHandler(&askOptions[1], AskDlg, 2);
1609 }
1610
1611 //---------------------------- Promotion Popup --------------------------------------
1612
1613 static int count;
1614
1615 static void PromoPick P((int n));
1616
1617 static Option promoOptions[] = {
1618 {   0,         0,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1619 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1620 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1621 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1622 {   0,  SAME_ROW,    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 | NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
1627 };
1628
1629 static void
1630 PromoPick (int n)
1631 {
1632     int promoChar = promoOptions[n+count].value;
1633
1634     PopDown(PromoDlg);
1635
1636     if (promoChar == 0) fromX = -1;
1637     if (fromX == -1) return;
1638
1639     if (! promoChar) {
1640         fromX = fromY = -1;
1641         ClearHighlights();
1642         return;
1643     }
1644     if(promoChar == '=' && !IS_SHOGI(gameInfo.variant)) promoChar = NULLCHAR;
1645     UserMoveEvent(fromX, fromY, toX, toY, promoChar);
1646
1647     if (!appData.highlightLastMove || gotPremove) ClearHighlights();
1648     if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
1649     fromX = fromY = -1;
1650 }
1651
1652 static void
1653 SetPromo (char *name, int nr, char promoChar)
1654 {
1655     ASSIGN(promoOptions[nr].name, name);
1656     promoOptions[nr].value = promoChar;
1657     promoOptions[nr].min = SAME_ROW;
1658 }
1659
1660 void
1661 PromotionPopUp (char choice)
1662 { // choice depends on variant: prepare dialog acordingly
1663   count = 8;
1664   SetPromo(_("Cancel"), --count, -1); // Beware: GenericPopUp cannot handle user buttons named "cancel" (lowe case)!
1665   if(choice != '+') {
1666     if (!appData.testLegality || gameInfo.variant == VariantSuicide ||
1667         gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove) ||
1668         gameInfo.variant == VariantGiveaway) {
1669       SetPromo(_("King"), --count, 'k');
1670     }
1671     if(gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove)) {
1672       SetPromo(_("Captain"), --count, 'c');
1673       SetPromo(_("Lieutenant"), --count, 'l');
1674       SetPromo(_("General"), --count, 'g');
1675       SetPromo(_("Warlord"), --count, 'w');
1676     } else {
1677       SetPromo(_("Knight"), --count, 'n');
1678       SetPromo(_("Bishop"), --count, 'b');
1679       SetPromo(_("Rook"), --count, 'r');
1680       if(gameInfo.variant == VariantCapablanca ||
1681          gameInfo.variant == VariantGothic ||
1682          gameInfo.variant == VariantCapaRandom) {
1683         SetPromo(_("Archbishop"), --count, 'a');
1684         SetPromo(_("Chancellor"), --count, 'c');
1685       }
1686       SetPromo(_("Queen"), --count, 'q');
1687       if(gameInfo.variant == VariantChuChess)
1688         SetPromo(_("Lion"), --count, 'l');
1689     }
1690   } else // [HGM] shogi
1691   {
1692       SetPromo(_("Defer"), --count, '=');
1693       SetPromo(_("Promote"), --count, '+');
1694   }
1695   promoOptions[count].min = 0;
1696   GenericPopUp(promoOptions + count, "Promotion", PromoDlg, BoardWindow, NONMODAL, 0);
1697 }
1698
1699 //---------------------------- Chat Windows ----------------------------------------------
1700
1701 static char *line, *memo, *partner, *texts[MAX_CHAT], dirty[MAX_CHAT];
1702 static int activePartner;
1703
1704 void ChatSwitch P((int n));
1705 int  ChatOK P((int n));
1706
1707 Option chatOptions[] = {
1708 { 0,   T_TOP,    100, NULL, (void*) &partner, NULL, NULL, TextBox, N_("Chat partner:") },
1709 { 1, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1710 { 2, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1711 { 3, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1712 { 4, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, "" },
1713 { 100, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &memo, NULL, NULL, TextBox, "" },
1714 {  0,    0,  510, NULL, (void*) &line, NULL, NULL, TextBox, "" },
1715 { 0, NO_OK|SAME_ROW, 0, NULL, (void*) &ChatOK, NULL, NULL, EndMark , "" }
1716 };
1717
1718 void
1719 OutputChatMessage (int partner, char *mess)
1720 {
1721     char *p = texts[partner];
1722     int len = strlen(mess) + 1;
1723
1724     if(p) len += strlen(p);
1725     texts[partner] = (char*) malloc(len);
1726     snprintf(texts[partner], len, "%s%s", p ? p : "", mess);
1727     FREE(p);
1728     if(partner == activePartner) {
1729         AppendText(&chatOptions[5], mess);
1730         SetInsertPos(&chatOptions[5], len-2);
1731     } else {
1732         SetColor("#FFC000", &chatOptions[partner + (partner < activePartner)]);
1733         dirty[partner] = 1;
1734     }
1735 }
1736
1737 int
1738 ChatOK (int n)
1739 {   // can only be called through <Enter> in chat-partner text-edit, as there is no OK button
1740     char buf[MSG_SIZ];
1741
1742     if(!partner || strcmp(partner, chatPartner[activePartner])) {
1743         safeStrCpy(chatPartner[activePartner], partner, MSG_SIZ);
1744         SetWidgetText(&chatOptions[5], "", -1); // clear text if we alter partner
1745         SetWidgetText(&chatOptions[6], "", ChatDlg); // clear text if we alter partner
1746         HardSetFocus(&chatOptions[6]);
1747     }
1748     if(line[0]) { // something was typed
1749         SetWidgetText(&chatOptions[6], "", ChatDlg);
1750         // from here on it could be back-end
1751         if(line[strlen(line)-1] == '\n') line[strlen(line)-1] = NULLCHAR;
1752         SaveInHistory(line);
1753         if(!strcmp("whispers", chatPartner[activePartner]))
1754               snprintf(buf, MSG_SIZ, "whisper %s\n", line); // WHISPER box uses "whisper" to send
1755         else if(!strcmp("shouts", chatPartner[activePartner]))
1756               snprintf(buf, MSG_SIZ, "shout %s\n", line); // SHOUT box uses "shout" to send
1757         else {
1758             if(!atoi(chatPartner[activePartner])) {
1759                 snprintf(buf, MSG_SIZ, "> %s\n", line); // echo only tells to handle, not channel
1760                 OutputChatMessage(activePartner, buf);
1761                 snprintf(buf, MSG_SIZ, "xtell %s %s\n", chatPartner[activePartner], line);
1762             } else
1763                 snprintf(buf, MSG_SIZ, "tell %s %s\n", chatPartner[activePartner], line);
1764         }
1765         SendToICS(buf);
1766     }
1767     return FALSE; // never pop down
1768 }
1769
1770 void
1771 ChatSwitch (int n)
1772 {
1773     int i, j;
1774     if(n <= activePartner) n--;
1775     activePartner = n;
1776     if(!texts[n]) texts[n] = strdup("");
1777     dirty[n] = 0;
1778     SetWidgetText(&chatOptions[5], texts[n], ChatDlg);
1779     SetInsertPos(&chatOptions[5], strlen(texts[n]));
1780     SetWidgetText(&chatOptions[0], chatPartner[n], ChatDlg);
1781     for(i=j=0; i<MAX_CHAT; i++) {
1782         if(i == activePartner) continue;
1783         SetWidgetLabel(&chatOptions[++j], chatPartner[i]);
1784         SetColor(dirty[i] ? "#FFC000" : "#FFFFFF", &chatOptions[j]);
1785     }
1786     SetWidgetText(&chatOptions[6], "", ChatDlg);
1787     HardSetFocus(&chatOptions[6]);
1788 }
1789
1790 void
1791 ChatProc ()
1792 {
1793     if(GenericPopUp(chatOptions, _("Chat box"), ChatDlg, BoardWindow, NONMODAL, appData.topLevel))
1794         AddHandler(&chatOptions[0], ChatDlg, 2), AddHandler(&chatOptions[6], ChatDlg, 2); // treats return as OK
1795     MarkMenu("View.OpenChatWindow", ChatDlg);
1796 }
1797
1798 //--------------------------------- Game-List options dialog ------------------------------------------
1799
1800 char *strings[LPUSERGLT_SIZE];
1801 int stringPtr;
1802
1803 void
1804 GLT_ClearList ()
1805 {
1806     strings[0] = NULL;
1807     stringPtr = 0;
1808 }
1809
1810 void
1811 GLT_AddToList (char *name)
1812 {
1813     strings[stringPtr++] = name;
1814     strings[stringPtr] = NULL;
1815 }
1816
1817 Boolean
1818 GLT_GetFromList (int index, char *name)
1819 {
1820   safeStrCpy(name, strings[index], MSG_SIZ);
1821   return TRUE;
1822 }
1823
1824 void
1825 GLT_DeSelectList ()
1826 {
1827 }
1828
1829 static void GLT_Button P((int n));
1830 static int GLT_OK P((int n));
1831
1832 static Option listOptions[] = {
1833 {300, LR|TB, 200, NULL, (void*) strings, "", NULL, ListBox, "" }, // For GTK we need to specify a height, as default would just show 3 lines
1834 { 0,    0,     0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("factory") },
1835 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("up") },
1836 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("down") },
1837 { 0, SAME_ROW, 0, NULL, (void*) &GLT_OK, "", NULL, EndMark , "" }
1838 };
1839
1840 static int
1841 GLT_OK (int n)
1842 {
1843     GLT_ParseList();
1844     appData.gameListTags = strdup(lpUserGLT);
1845     return 1;
1846 }
1847
1848 static void
1849 GLT_Button (int n)
1850 {
1851     int index = SelectedListBoxItem (&listOptions[0]);
1852     char *p;
1853     if (index < 0) {
1854         DisplayError(_("No tag selected"), 0);
1855         return;
1856     }
1857     p = strings[index];
1858     if (n == 3) {
1859         if(index >= strlen(GLT_ALL_TAGS)) return;
1860         strings[index] = strings[index+1];
1861         strings[++index] = p;
1862         LoadListBox(&listOptions[0], "?", index, index-1); // only change the two specified entries
1863     } else
1864     if (n == 2) {
1865         if(index == 0) return;
1866         strings[index] = strings[index-1];
1867         strings[--index] = p;
1868         LoadListBox(&listOptions[0], "?", index, index+1);
1869     } else
1870     if (n == 1) {
1871       safeStrCpy(lpUserGLT, GLT_DEFAULT_TAGS, LPUSERGLT_SIZE);
1872       GLT_TagsToList(lpUserGLT);
1873       index = 0;
1874       LoadListBox(&listOptions[0], "?", -1, -1);
1875     }
1876     HighlightListBoxItem(&listOptions[0], index);
1877 }
1878
1879 void
1880 GameListOptionsPopUp (DialogClass parent)
1881 {
1882     safeStrCpy(lpUserGLT, appData.gameListTags, LPUSERGLT_SIZE);
1883     GLT_TagsToList(lpUserGLT);
1884
1885     GenericPopUp(listOptions, _("Game-list options"), TransientDlg, parent, MODAL, 0);
1886 }
1887
1888 void
1889 GameListOptionsProc ()
1890 {
1891     GameListOptionsPopUp(BoardWindow);
1892 }
1893
1894 //----------------------------- Error popup in various uses -----------------------------
1895
1896 /*
1897  * [HGM] Note:
1898  * XBoard has always had some pathologic behavior with multiple simultaneous error popups,
1899  * (which can occur even for modal popups when asynchrounous events, e.g. caused by engine, request a popup),
1900  * and this new implementation reproduces that as well:
1901  * Only the shell of the last instance is remembered in shells[ErrorDlg] (which replaces errorShell),
1902  * so that PopDowns ordered from the code always refer to that instance, and once that is down,
1903  * have no clue as to how to reach the others. For the Delete Window button calling PopDown this
1904  * has now been repaired, as the action routine assigned to it gets the shell passed as argument.
1905  */
1906
1907 int errorUp = False;
1908
1909 void
1910 ErrorPopDown ()
1911 {
1912     if (!errorUp) return;
1913     dialogError = errorUp = False;
1914     PopDown(ErrorDlg); PopDown(FatalDlg); // on explicit request we pop down any error dialog
1915     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
1916 }
1917
1918 static int
1919 ErrorOK (int n)
1920 {
1921     dialogError = errorUp = False;
1922     PopDown(n == 1 ? FatalDlg : ErrorDlg); // kludge: non-modal dialogs have one less (dummy) option
1923     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
1924     return FALSE; // prevent second Popdown !
1925 }
1926
1927 static Option errorOptions[] = {
1928 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // dummy option: will never be displayed
1929 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // textValue field will be set before popup
1930 { 0,NO_CANCEL,0, NULL, (void*) &ErrorOK, "", NULL, EndMark , "" }
1931 };
1932
1933 void
1934 ErrorPopUp (char *title, char *label, int modal)
1935 {
1936     errorUp = True;
1937     errorOptions[1].name = label;
1938     if(dialogError = shellUp[TransientDlg])
1939         GenericPopUp(errorOptions+1, title, FatalDlg, TransientDlg, MODAL, 0); // pop up as daughter of the transient dialog
1940     else
1941         GenericPopUp(errorOptions+modal, title, modal ? FatalDlg: ErrorDlg, BoardWindow, modal, 0); // kludge: option start address indicates modality
1942 }
1943
1944 void
1945 DisplayError (String message, int error)
1946 {
1947     char buf[MSG_SIZ];
1948
1949     if (error == 0) {
1950         if (appData.debugMode || appData.matchMode) {
1951             fprintf(stderr, "%s: %s\n", programName, message);
1952         }
1953     } else {
1954         if (appData.debugMode || appData.matchMode) {
1955             fprintf(stderr, "%s: %s: %s\n",
1956                     programName, message, strerror(error));
1957         }
1958         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
1959         message = buf;
1960     }
1961     ErrorPopUp(_("Error"), message, FALSE);
1962 }
1963
1964
1965 void
1966 DisplayMoveError (String message)
1967 {
1968     fromX = fromY = -1;
1969     ClearHighlights();
1970     DrawPosition(TRUE, NULL); // selective redraw would miss the from-square of the rejected move, displayed empty after drag, but not marked damaged!
1971     if (appData.debugMode || appData.matchMode) {
1972         fprintf(stderr, "%s: %s\n", programName, message);
1973     }
1974     if (appData.popupMoveErrors) {
1975         ErrorPopUp(_("Error"), message, FALSE);
1976     } else {
1977         DisplayMessage(message, "");
1978     }
1979 }
1980
1981
1982 void
1983 DisplayFatalError (String message, int error, int status)
1984 {
1985     char buf[MSG_SIZ];
1986
1987     errorExitStatus = status;
1988     if (error == 0) {
1989         fprintf(stderr, "%s: %s\n", programName, message);
1990     } else {
1991         fprintf(stderr, "%s: %s: %s\n",
1992                 programName, message, strerror(error));
1993         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
1994         message = buf;
1995     }
1996     if(mainOptions[W_BOARD].handle) {
1997         if (appData.popupExitMessage) {
1998             ErrorPopUp(status ? _("Fatal Error") : _("Exiting"), message, TRUE);
1999         } else {
2000             ExitEvent(status);
2001         }
2002     }
2003 }
2004
2005 void
2006 DisplayInformation (String message)
2007 {
2008     ErrorPopDown();
2009     ErrorPopUp(_("Information"), message, TRUE);
2010 }
2011
2012 void
2013 DisplayNote (String message)
2014 {
2015     ErrorPopDown();
2016     ErrorPopUp(_("Note"), message, FALSE);
2017 }
2018
2019 void
2020 DisplayTitle (char *text)
2021 {
2022     char title[MSG_SIZ];
2023     char icon[MSG_SIZ];
2024
2025     if (text == NULL) text = "";
2026
2027     if(partnerUp) { SetDialogTitle(DummyDlg, text); return; }
2028
2029     if (*text != NULLCHAR) {
2030       safeStrCpy(icon, text, sizeof(icon)/sizeof(icon[0]) );
2031       safeStrCpy(title, text, sizeof(title)/sizeof(title[0]) );
2032     } else if (appData.icsActive) {
2033         snprintf(icon, sizeof(icon), "%s", appData.icsHost);
2034         snprintf(title, sizeof(title), "%s: %s", programName, appData.icsHost);
2035     } else if (appData.cmailGameName[0] != NULLCHAR) {
2036         snprintf(icon, sizeof(icon), "%s", "CMail");
2037         snprintf(title,sizeof(title), "%s: %s", programName, "CMail");
2038 #ifdef GOTHIC
2039     // [HGM] license: This stuff should really be done in back-end, but WinBoard already had a pop-up for it
2040     } else if (gameInfo.variant == VariantGothic) {
2041       safeStrCpy(icon,  programName, sizeof(icon)/sizeof(icon[0]) );
2042       safeStrCpy(title, GOTHIC,     sizeof(title)/sizeof(title[0]) );
2043 #endif
2044 #ifdef FALCON
2045     } else if (gameInfo.variant == VariantFalcon) {
2046       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
2047       safeStrCpy(title, FALCON, sizeof(title)/sizeof(title[0]) );
2048 #endif
2049     } else if (appData.noChessProgram) {
2050       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
2051       safeStrCpy(title, programName, sizeof(title)/sizeof(title[0]) );
2052     } else {
2053       safeStrCpy(icon, first.tidy, sizeof(icon)/sizeof(icon[0]) );
2054         snprintf(title,sizeof(title), "%s: %s", programName, first.tidy);
2055     }
2056     SetWindowTitle(text, title, icon);
2057 }
2058
2059 #define PAUSE_BUTTON "P"
2060 #define PIECE_MENU_SIZE 18
2061 static String pieceMenuStrings[2][PIECE_MENU_SIZE+1] = {
2062     { N_("White"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
2063       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
2064       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
2065       N_("Empty square"), N_("Clear board"), NULL },
2066     { N_("Black"), "----", 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 };
2071 /* must be in same order as pieceMenuStrings! */
2072 static ChessSquare pieceMenuTranslation[2][PIECE_MENU_SIZE] = {
2073     { WhitePlay, (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2074         WhiteRook, WhiteQueen, WhiteKing, (ChessSquare) 0, WhiteAlfil,
2075         WhiteCannon, WhiteAngel, WhiteMarshall, (ChessSquare) 0,
2076         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2077     { BlackPlay, (ChessSquare) 0, BlackPawn, BlackKnight, BlackBishop,
2078         BlackRook, BlackQueen, BlackKing, (ChessSquare) 0, BlackAlfil,
2079         BlackCannon, BlackAngel, BlackMarshall, (ChessSquare) 0,
2080         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2081 };
2082
2083 #define DROP_MENU_SIZE 6
2084 static String dropMenuStrings[DROP_MENU_SIZE+1] = {
2085     "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"), N_("Queen"), NULL
2086   };
2087 /* must be in same order as dropMenuStrings! */
2088 static ChessSquare dropMenuTranslation[DROP_MENU_SIZE] = {
2089     (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2090     WhiteRook, WhiteQueen
2091 };
2092
2093 // [HGM] experimental code to pop up window just like the main window, using GenercicPopUp
2094
2095 static Option *Exp P((int n, int x, int y));
2096 void MenuCallback P((int n));
2097 void SizeKludge P((int n));
2098 static Option *LogoW P((int n, int x, int y));
2099 static Option *LogoB P((int n, int x, int y));
2100
2101 static int pmFromX = -1, pmFromY = -1;
2102 void *userLogo;
2103
2104 void
2105 DisplayLogos (Option *w1, Option *w2)
2106 {
2107         void *whiteLogo = first.programLogo, *blackLogo = second.programLogo;
2108         if(appData.autoLogo) {
2109
2110           switch(gameMode) { // pick logos based on game mode
2111             case IcsObserving:
2112                 whiteLogo = second.programLogo; // ICS logo
2113                 blackLogo = second.programLogo;
2114             default:
2115                 break;
2116             case IcsPlayingWhite:
2117                 if(!appData.zippyPlay) whiteLogo = userLogo;
2118                 blackLogo = second.programLogo; // ICS logo
2119                 break;
2120             case IcsPlayingBlack:
2121                 whiteLogo = second.programLogo; // ICS logo
2122                 blackLogo = appData.zippyPlay ? first.programLogo : userLogo;
2123                 break;
2124             case TwoMachinesPlay:
2125                 if(first.twoMachinesColor[0] == 'b') {
2126                     whiteLogo = second.programLogo;
2127                     blackLogo = first.programLogo;
2128                 }
2129                 break;
2130             case MachinePlaysWhite:
2131                 blackLogo = userLogo;
2132                 break;
2133             case MachinePlaysBlack:
2134                 whiteLogo = userLogo;
2135                 blackLogo = first.programLogo;
2136           }
2137         }
2138         DrawLogo(w1, whiteLogo);
2139         DrawLogo(w2, blackLogo);
2140 }
2141
2142 static void
2143 PMSelect (int n)
2144 {   // user callback for board context menus
2145     if (pmFromX < 0 || pmFromY < 0) return;
2146     if(n == W_DROP) DropMenuEvent(dropMenuTranslation[values[n]], pmFromX, pmFromY);
2147     else EditPositionMenuEvent(pieceMenuTranslation[n - W_MENUW][values[n]], pmFromX, pmFromY);
2148 }
2149
2150 static void
2151 CCB (int n)
2152 {
2153     shiftKey = (ShiftKeys() & 3) != 0;
2154     if(n < 0) { // button != 1
2155         n = -n;
2156         if(shiftKey && (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack)) {
2157             AdjustClock(n == W_BLACK, 1);
2158         }
2159     } else
2160     ClockClick(n == W_BLACK);
2161 }
2162
2163 Option mainOptions[] = { // description of main window in terms of generic dialog creator
2164 { 0, 0xCA, 0, NULL, NULL, "", NULL, BarBegin, "" }, // menu bar
2165   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("File") },
2166   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Edit") },
2167   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("View") },
2168   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Mode") },
2169   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Action") },
2170   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Engine") },
2171   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Options") },
2172   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Help") },
2173 { 0, 0, 0, NULL, (void*)&SizeKludge, "", NULL, BarEnd, "" },
2174 { 0, LR|T2T|BORDER|SAME_ROW, 0, NULL, NULL, "", NULL, Label, "1" }, // optional title in window
2175 { 50,    LL|TT,            100, NULL, (void*) &LogoW, NULL, NULL, Skip, "" }, // white logo
2176 { 12,   L2L|T2T,           200, NULL, (void*) &CCB, NULL, NULL, Label, "White" }, // white clock
2177 { 13,   R2R|T2T|SAME_ROW,  200, NULL, (void*) &CCB, NULL, NULL, Label, "Black" }, // black clock
2178 { 50,    RR|TT|SAME_ROW,   100, NULL, (void*) &LogoB, NULL, NULL, Skip, "" }, // black logo
2179 { 0, LR|T2T|BORDER,        401, NULL, NULL, "", NULL, Skip, "2" }, // backup for title in window (if no room for other)
2180 { 0, LR|T2T|BORDER,        270, NULL, NULL, "", NULL, Label, "message" }, // message field
2181 { 0, RR|TT|SAME_ROW,       125, NULL, NULL, "", NULL, BoxBegin, "" }, // (optional) button bar
2182   { 0,    0,     0, NULL, (void*) &ToStartEvent, NULL, NULL, Button, N_("<<") },
2183   { 0, SAME_ROW, 0, NULL, (void*) &BackwardEvent, NULL, NULL, Button, N_("<") },
2184   { 0, SAME_ROW, 0, NULL, (void*) &PauseEvent, NULL, NULL, Button, N_(PAUSE_BUTTON) },
2185   { 0, SAME_ROW, 0, NULL, (void*) &ForwardEvent, NULL, NULL, Button, N_(">") },
2186   { 0, SAME_ROW, 0, NULL, (void*) &ToEndEvent, NULL, NULL, Button, N_(">>") },
2187 { 0, 0, 0, NULL, NULL, "", NULL, BoxEnd, "" },
2188 { 401, LR|TB, 401, NULL, (char*) &Exp, NULL, NULL, Graph, "shadow board" }, // board
2189   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[0], PopUp, "menuW" },
2190   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[1], PopUp, "menuB" },
2191   { -1, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, dropMenuStrings, PopUp, "menuD" },
2192 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2193 };
2194
2195 Option *
2196 LogoW (int n, int x, int y)
2197 {
2198     if(n == 10) DisplayLogos(&mainOptions[W_WHITE-1], NULL);
2199     return NULL;
2200 }
2201
2202 Option *
2203 LogoB (int n, int x, int y)
2204 {
2205     if(n == 10) DisplayLogos(NULL, &mainOptions[W_BLACK+1]);
2206     return NULL;
2207 }
2208
2209 void
2210 SizeKludge (int n)
2211 {   // callback called by GenericPopUp immediately after sizing the menu bar
2212     int width = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2213     int w = width - 44 - mainOptions[n].min;
2214     mainOptions[W_TITLE].max = w; // width left behind menu bar
2215     if(w < 0.4*width) // if no reasonable amount of space for title, force small layout
2216         mainOptions[W_SMALL].type = mainOptions[W_TITLE].type, mainOptions[W_TITLE].type = Skip;
2217 }
2218
2219 void
2220 MenuCallback (int n)
2221 {
2222     MenuProc *proc = (MenuProc *) (((MenuItem*)(mainOptions[n].choice))[values[n]].proc);
2223
2224     if(!proc) RecentEngineEvent(values[n] - firstEngineItem); else (proc)();
2225 }
2226
2227 static Option *
2228 Exp (int n, int x, int y)
2229 {
2230     static int but1, but3, oldW, oldH;
2231     int menuNr = -3, sizing, f, r;
2232
2233     if(n == 0) { // motion
2234         if(SeekGraphClick(Press, x, y, 1)) return NULL;
2235         if((but1 || dragging == 2) && !PromoScroll(x, y)) DragPieceMove(x, y);
2236         if(but3) MovePV(x, y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
2237         if(appData.highlightDragging) {
2238             f = EventToSquare(x, BOARD_WIDTH);  if ( flipView && f >= 0) f = BOARD_WIDTH - 1 - f;
2239             r = EventToSquare(y, BOARD_HEIGHT); if (!flipView && r >= 0) r = BOARD_HEIGHT - 1 - r;
2240             HoverEvent(x, y, f, r);
2241         }
2242         return NULL;
2243     }
2244     if(n != 10 && PopDown(PromoDlg)) fromX = fromY = -1; // user starts fiddling with board when promotion dialog is up
2245     shiftKey = ShiftKeys();
2246     controlKey = (shiftKey & 0xC) != 0;
2247     shiftKey = (shiftKey & 3) != 0;
2248     switch(n) {
2249         case  1: LeftClick(Press,   x, y), but1 = 1; break;
2250         case -1: LeftClick(Release, x, y), but1 = 0; break;
2251         case  2: shiftKey = !shiftKey;
2252         case  3: menuNr = RightClick(Press,   x, y, &pmFromX, &pmFromY), but3 = 1; break;
2253         case -2: shiftKey = !shiftKey;
2254         case -3: menuNr = RightClick(Release, x, y, &pmFromX, &pmFromY), but3 = 0; break;
2255         case 10:
2256             sizing = (oldW != x || oldH != y);
2257             oldW = x; oldH = y;
2258             InitDrawingHandle(mainOptions + W_BOARD);
2259             if(sizing) return NULL; // don't redraw while sizing
2260             DrawPosition(True, NULL);
2261         default:
2262             return NULL;
2263     }
2264
2265     switch(menuNr) {
2266       case 0: return &mainOptions[shiftKey ? W_MENUW: W_MENUB];
2267       case 1: SetupDropMenu(); return &mainOptions[W_DROP];
2268       case 2:
2269       case -1: ErrorPopDown();
2270       case -2:
2271       default: break; // -3, so no clicks caught
2272     }
2273     return NULL;
2274 }
2275
2276 Option *
2277 BoardPopUp (int squareSize, int lineGap, void *clockFontThingy)
2278 {
2279     int i, size = BOARD_WIDTH*(squareSize + lineGap) + lineGap, logo = appData.logoSize;
2280     mainOptions[W_WHITE].choice = (char**) clockFontThingy;
2281     mainOptions[W_BLACK].choice = (char**) clockFontThingy;
2282     mainOptions[W_BOARD].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2283     mainOptions[W_BOARD].max = mainOptions[W_SMALL].max = size; // board size
2284     mainOptions[W_SMALL].max = size - 2; // board title (subtract border!)
2285     mainOptions[W_BLACK].max = mainOptions[W_WHITE].max = size/2-3; // clock width
2286     mainOptions[W_MESSG].max = appData.showButtonBar ? size-135 : size-2; // message
2287     mainOptions[W_MENU].max = size-40; // menu bar
2288     mainOptions[W_TITLE].type = appData.titleInWindow ? Label : Skip ;
2289     if(logo && logo <= size/4) { // Activate logos
2290         mainOptions[W_WHITE-1].type = mainOptions[W_BLACK+1].type = Graph;
2291         mainOptions[W_WHITE-1].max  = mainOptions[W_BLACK+1].max  = logo;
2292         mainOptions[W_WHITE-1].value= mainOptions[W_BLACK+1].value= logo/2;
2293         mainOptions[W_WHITE].min  |= SAME_ROW;
2294         mainOptions[W_WHITE].max  = mainOptions[W_BLACK].max  -= logo + 4;
2295         mainOptions[W_WHITE].name = mainOptions[W_BLACK].name = "Double\nHeight";
2296     }
2297     if(!appData.showButtonBar) for(i=W_BUTTON; i<W_BOARD; i++) mainOptions[i].type = Skip;
2298     for(i=0; i<8; i++) mainOptions[i+1].choice = (char**) menuBar[i].mi;
2299     AppendEnginesToMenu(appData.recentEngineList);
2300     GenericPopUp(mainOptions, "XBoard", BoardWindow, BoardWindow, NONMODAL, 1); // allways top-level
2301     return mainOptions;
2302 }
2303
2304 static Option *
2305 SlaveExp (int n, int x, int y)
2306 {
2307     if(n == 10) { // expose event
2308         flipView = !flipView; partnerUp = !partnerUp;
2309         DrawPosition(True, NULL); // [HGM] dual: draw other board in other orientation
2310         flipView = !flipView; partnerUp = !partnerUp;
2311     }
2312     return NULL;
2313 }
2314
2315 Option dualOptions[] = { // auxiliary board window
2316 { 0, L2L|T2T,              198, NULL, NULL, NULL, NULL, Label, "White" }, // white clock
2317 { 0, R2R|T2T|SAME_ROW,     198, NULL, NULL, NULL, NULL, Label, "Black" }, // black clock
2318 { 0, LR|T2T|BORDER,        401, NULL, NULL, NULL, NULL, Label, "This feature is experimental" }, // message field
2319 { 401, LR|TT, 401, NULL, (char*) &SlaveExp, NULL, NULL, Graph, "shadow board" }, // board
2320 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2321 };
2322
2323 void
2324 SlavePopUp ()
2325 {
2326     int size = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2327     // copy params from main board
2328     dualOptions[0].choice = mainOptions[W_WHITE].choice;
2329     dualOptions[1].choice = mainOptions[W_BLACK].choice;
2330     dualOptions[3].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2331     dualOptions[3].max = dualOptions[2].max = size; // board width
2332     dualOptions[0].max = dualOptions[1].max = size/2 - 3; // clock width
2333     GenericPopUp(dualOptions, "XBoard", DummyDlg, BoardWindow, NONMODAL, appData.topLevel);
2334     SlaveResize(dualOptions+3);
2335 }
2336
2337 void
2338 DisplayWhiteClock (long timeRemaining, int highlight)
2339 {
2340     if(appData.noGUI) return;
2341     if(twoBoards && partnerUp) {
2342         DisplayTimerLabel(&dualOptions[0], _("White"), timeRemaining, highlight);
2343         return;
2344     }
2345     DisplayTimerLabel(&mainOptions[W_WHITE], _("White"), timeRemaining, highlight);
2346     if(highlight) SetClockIcon(0);
2347 }
2348
2349 void
2350 DisplayBlackClock (long timeRemaining, int highlight)
2351 {
2352     if(appData.noGUI) return;
2353     if(twoBoards && partnerUp) {
2354         DisplayTimerLabel(&dualOptions[1], _("Black"), timeRemaining, highlight);
2355         return;
2356     }
2357     DisplayTimerLabel(&mainOptions[W_BLACK], _("Black"), timeRemaining, highlight);
2358     if(highlight) SetClockIcon(1);
2359 }
2360
2361
2362 //---------------------------------------------
2363
2364 void
2365 DisplayMessage (char *message, char *extMessage)
2366 {
2367   /* display a message in the message widget */
2368
2369   char buf[MSG_SIZ];
2370
2371   if (extMessage)
2372     {
2373       if (*message)
2374         {
2375           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
2376           message = buf;
2377         }
2378       else
2379         {
2380           message = extMessage;
2381         };
2382     };
2383
2384     safeStrCpy(lastMsg, message, MSG_SIZ); // [HGM] make available
2385
2386   /* need to test if messageWidget already exists, since this function
2387      can also be called during the startup, if for example a Xresource
2388      is not set up correctly */
2389   if(mainOptions[W_MESSG].handle)
2390     SetWidgetLabel(&mainOptions[W_MESSG], message);
2391
2392   return;
2393 }
2394
2395 //----------------------------------- File Browser -------------------------------
2396
2397 #ifdef HAVE_DIRENT_H
2398 #include <dirent.h>
2399 #else
2400 #include <sys/dir.h>
2401 #define dirent direct
2402 #endif
2403
2404 #include <sys/stat.h>
2405
2406 #define MAXFILES 1000
2407
2408 static ChessProgramState *savCps;
2409 static FILE **savFP;
2410 static char *fileName, *extFilter, *savMode, **namePtr;
2411 static int folderPtr, filePtr, oldVal, byExtension, extFlag, pageStart, cnt;
2412 static char curDir[MSG_SIZ], title[MSG_SIZ], *folderList[MAXFILES], *fileList[MAXFILES];
2413
2414 static char *FileTypes[] = {
2415 "Chess Games",
2416 "Chess Positions",
2417 "Tournaments",
2418 "Opening Books",
2419 "Sound files",
2420 "Images",
2421 "Settings (*.ini)",
2422 "Log files",
2423 "All files",
2424 NULL,
2425 "PGN",
2426 "Old-Style Games",
2427 "FEN",
2428 "Old-Style Positions",
2429 NULL,
2430 NULL
2431 };
2432
2433 static char *Extensions[] = {
2434 ".pgn .game",
2435 ".fen .epd .pos",
2436 ".trn",
2437 ".bin",
2438 ".wav",
2439 ".ini",
2440 ".log",
2441 "",
2442 "INVALID",
2443 ".pgn",
2444 ".game",
2445 ".fen",
2446 ".pos",
2447 NULL,
2448 ""
2449 };
2450
2451 void DirSelProc P((int n, int sel));
2452 void FileSelProc P((int n, int sel));
2453 void SetTypeFilter P((int n));
2454 int BrowseOK P((int n));
2455 void Switch P((int n));
2456 void CreateDir P((int n));
2457
2458 Option browseOptions[] = {
2459 {   0,    LR|T2T,      500, NULL, NULL, NULL, NULL, Label, title },
2460 {   0,    L2L|T2T,     250, NULL, NULL, NULL, NULL, Label, N_("Directories:") },
2461 {   0,R2R|T2T|SAME_ROW,100, NULL, NULL, NULL, NULL, Label, N_("Files:") },
2462 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by name") },
2463 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by type") },
2464 { 300,    L2L|TB,      250, NULL, (void*) folderList, (char*) &DirSelProc, NULL, ListBox, "" },
2465 { 300, R2R|TB|SAME_ROW,250, NULL, (void*) fileList, (char*) &FileSelProc, NULL, ListBox, "" },
2466 {   0,       0,        300, NULL, (void*) &fileName, NULL, NULL, TextBox, N_("Filename:") },
2467 {   0,    SAME_ROW,    120, NULL, (void*) &CreateDir, NULL, NULL, Button, N_("New directory") },
2468 {   0, COMBO_CALLBACK, 150, NULL, (void*) &SetTypeFilter, NULL, FileTypes, ComboBox, N_("File type:") },
2469 {   0,    SAME_ROW,      0, NULL, (void*) &BrowseOK, "", NULL, EndMark , "" }
2470 };
2471
2472 int
2473 BrowseOK (int n)
2474 {
2475         if(!fileName[0]) { // it is enough to have a file selected
2476             if(browseOptions[6].textValue) { // kludge: if callback specified we browse for file
2477                 int sel = SelectedListBoxItem(&browseOptions[6]);
2478                 if(sel < 0 || sel >= filePtr) return FALSE;
2479                 ASSIGN(fileName, fileList[sel]);
2480             } else { // we browse for path
2481                 ASSIGN(fileName, curDir); // kludge: without callback we browse for path
2482             }
2483         }
2484         if(!fileName[0]) return FALSE; // refuse OK when no file
2485         if(!savMode[0]) { // browsing for name only (dialog Browse button)
2486                 if(fileName[0] == '/') // We already had a path name
2487                     snprintf(title, MSG_SIZ, "%s", fileName);
2488                 else
2489                     snprintf(title, MSG_SIZ, "%s/%s", curDir, fileName);
2490                 SetWidgetText((Option*) savFP, title, TransientDlg);
2491                 currentCps = savCps; // could return to Engine Settings dialog!
2492                 return TRUE;
2493         }
2494         *savFP = fopen(fileName, savMode);
2495         if(*savFP == NULL) return FALSE; // refuse OK if file not openable
2496         ASSIGN(*namePtr, fileName);
2497         ScheduleDelayedEvent(DelayedLoad, 50);
2498         currentCps = savCps; // not sure this is ever non-null
2499         return TRUE;
2500 }
2501
2502 int
2503 AlphaNumCompare (char *p, char *q)
2504 {
2505     while(*p) {
2506         if(isdigit(*p) && isdigit(*q) && atoi(p) != atoi(q))
2507              return (atoi(p) > atoi(q) ? 1 : -1);
2508         if(*p != *q) break;
2509         p++, q++;
2510     }
2511     if(*p == *q) return 0;
2512     return (*p > *q ? 1 : -1);
2513 }
2514
2515 int
2516 Comp (const void *s, const void *t)
2517 {
2518     char *p = *(char**) s, *q = *(char**) t;
2519     if(extFlag) {
2520         char *h; int r;
2521         while(h = strchr(p, '.')) p = h+1;
2522         if(p == *(char**) s) p = "";
2523         while(h = strchr(q, '.')) q = h+1;
2524         if(q == *(char**) t) q = "";
2525         r = AlphaNumCompare(p, q);
2526         if(r) return r;
2527     }
2528     return AlphaNumCompare( *(char**) s, *(char**) t );
2529 }
2530
2531 void
2532 ListDir (int pathFlag)
2533 {
2534         DIR *dir;
2535         struct dirent *dp;
2536         struct stat statBuf;
2537         static int lastFlag;
2538
2539         if(pathFlag < 0) pathFlag = lastFlag;
2540         lastFlag = pathFlag;
2541         dir = opendir(".");
2542         getcwd(curDir, MSG_SIZ);
2543         snprintf(title, MSG_SIZ, "%s   %s", _("Contents of"), curDir);
2544         folderPtr = filePtr = cnt = 0; // clear listing
2545
2546         while (dp = readdir(dir)) { // pass 1: list foders
2547             char *s = dp->d_name;
2548             if(!stat(s, &statBuf) && S_ISDIR(statBuf.st_mode)) { // stat succeeds and tells us it is directory
2549                 if(s[0] == '.' && strcmp(s, "..")) continue; // suppress hidden, except ".."
2550                 ASSIGN(folderList[folderPtr], s); if(folderPtr < MAXFILES-2) folderPtr++;
2551             } else if(!pathFlag) {
2552                 char *s = dp->d_name, match=0;
2553 //              if(cnt == pageStart) { ASSIGN }
2554                 if(s[0] == '.') continue; // suppress hidden files
2555                 if(extFilter[0]) { // [HGM] filter on extension
2556                     char *p = extFilter, *q;
2557                     do {
2558                         if(q = strchr(p, ' ')) *q = 0;
2559                         if(strstr(s, p)) match++;
2560                         if(q) *q = ' ';
2561                     } while(q && (p = q+1));
2562                     if(!match) continue;
2563                 }
2564                 if(filePtr == MAXFILES-2) continue;
2565                 if(cnt++ < pageStart) continue;
2566                 ASSIGN(fileList[filePtr], s); filePtr++;
2567             }
2568         }
2569         if(filePtr == MAXFILES-2) { ASSIGN(fileList[filePtr], _("  next page")); filePtr++; }
2570         FREE(folderList[folderPtr]); folderList[folderPtr] = NULL;
2571         FREE(fileList[filePtr]); fileList[filePtr] = NULL;
2572         closedir(dir);
2573         extFlag = 0;         qsort((void*)folderList, folderPtr, sizeof(char*), &Comp);
2574         extFlag = byExtension; qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
2575 }
2576
2577 void
2578 Refresh (int pathFlag)
2579 {
2580     ListDir(pathFlag); // and make new one
2581     LoadListBox(&browseOptions[5], "", -1, -1);
2582     LoadListBox(&browseOptions[6], "", -1, -1);
2583     SetWidgetLabel(&browseOptions[0], title);
2584 }
2585
2586 static char msg1[] = N_("FIRST TYPE DIRECTORY NAME HERE");
2587 static char msg2[] = N_("TRY ANOTHER NAME");
2588
2589 void
2590 CreateDir (int n)
2591 {
2592     char *name, *errmsg = "";
2593     GetWidgetText(&browseOptions[n-1], &name);
2594     if(!strcmp(name, msg1) || !strcmp(name, msg2)) return;
2595     if(!name[0]) errmsg = _(msg1); else
2596     if(mkdir(name, 0755)) errmsg = _(msg2);
2597     else {
2598         chdir(name);
2599         Refresh(-1);
2600     }
2601     SetWidgetText(&browseOptions[n-1], errmsg, BrowserDlg);
2602 }
2603
2604 void
2605 Switch (int n)
2606 {
2607     if(byExtension == (n == 4)) return;
2608     extFlag = byExtension = (n == 4);
2609     qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
2610     LoadListBox(&browseOptions[6], "", -1, -1);
2611 }
2612
2613 void
2614 SetTypeFilter (int n)
2615 {
2616     int j = values[n];
2617     if(j == browseOptions[n].value) return; // no change
2618     browseOptions[n].value = j;
2619     SetWidgetLabel(&browseOptions[n], FileTypes[j]);
2620     ASSIGN(extFilter, Extensions[j]);
2621     pageStart = 0;
2622     Refresh(-1); // uses pathflag remembered by ListDir
2623     values[n] = oldVal; // do not disturb combo settings of underlying dialog
2624 }
2625
2626 void
2627 FileSelProc (int n, int sel)
2628 {
2629     if(sel < 0 || fileList[sel] == NULL) return;
2630     if(sel == MAXFILES-2) { pageStart = cnt; Refresh(-1); return; }
2631     ASSIGN(fileName, fileList[sel]);
2632     if(BrowseOK(0)) PopDown(BrowserDlg);
2633 }
2634
2635 void
2636 DirSelProc (int n, int sel)
2637 {
2638     if(!chdir(folderList[sel])) { // cd succeeded, so we are in new directory now
2639         Refresh(-1);
2640     }
2641 }
2642
2643 void
2644 Browse (DialogClass dlg, char *label, char *proposed, char *ext, Boolean pathFlag, char *mode, char **name, FILE **fp)
2645 {
2646     int j=0;
2647     savFP = fp; savMode = mode, namePtr = name, savCps = currentCps, oldVal = values[9]; // save params, for use in callback
2648     ASSIGN(extFilter, ext);
2649     ASSIGN(fileName, proposed ? proposed : "");
2650     for(j=0; Extensions[j]; j++) // look up actual value in list of possible values, to get selection nr
2651         if(extFilter && !strcmp(extFilter, Extensions[j])) break;
2652     if(Extensions[j] == NULL) { j++; ASSIGN(FileTypes[j], extFilter); }
2653     browseOptions[9].value = j;
2654     browseOptions[6].textValue = (char*) (pathFlag ? NULL : &FileSelProc); // disable file listbox during path browsing
2655     pageStart = 0; ListDir(pathFlag);
2656     currentCps = NULL;
2657     GenericPopUp(browseOptions, label, BrowserDlg, dlg, MODAL, 0);
2658     SetWidgetLabel(&browseOptions[9], FileTypes[j]);
2659 }
2660
2661 static char *openName;
2662 FileProc fileProc;
2663 char *fileOpenMode;
2664 FILE *openFP;
2665
2666 void
2667 DelayedLoad ()
2668 {
2669   (void) (*fileProc)(openFP, 0, openName);
2670 }
2671
2672 void
2673 FileNamePopUp (char *label, char *def, char *filter, FileProc proc, char *openMode)
2674 {
2675     fileProc = proc;            /* I can't see a way not */
2676     fileOpenMode = openMode;    /*   to use globals here */
2677     FileNamePopUpWrapper(label, def, filter, proc, False, openMode, &openName, &openFP);
2678 }
2679
2680 void
2681 ActivateTheme (int col)
2682 {
2683 }
2684
2685 char *
2686 Col2Text (int n)
2687 {
2688     return NULL;
2689 }