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