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