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