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