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