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