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