Use dataDir/manDir variables always
[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 dataDir[MSG_SIZ] = DATADIR, 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 static char *
2450 ReadLine (FILE *f)
2451 {
2452     static char buf[MSG_SIZ];
2453     int i = 0, c;
2454     while((c = fgetc(f)) != '\n') { if(c == EOF) return NULL; buf[i++] = c; }
2455     buf[i] = NULLCHAR;
2456     return buf;
2457 }
2458
2459 void
2460 GetHelpText (FILE *f, char *name)
2461 {
2462     char *line, buf[MSG_SIZ], title[MSG_SIZ], text[10000], *p = text, *q = text;
2463     int len, cnt = 0;
2464     snprintf(buf, MSG_SIZ, ".B %s", name);
2465     len = strlen(buf);
2466     for(len=1; buf[len] == ' ' || buf[len] == '-' || isalpha(buf[len]) || isdigit(buf[len]); len++);
2467     buf[len] = NULLCHAR;
2468     while(buf[--len] == ' ') buf[len] = NULLCHAR;
2469     snprintf(title, MSG_SIZ, "Help on '%s'", buf+3);
2470     while((line = ReadLine(f))) {
2471         if(!strncmp(line, buf, len) || !strncmp(line, ".SS ", 4) && !strncmp(line+4, buf+3, len-3)) {
2472             while((line = ReadLine(f)) && (cnt == 0 || strncmp(line, ".B ", 3) && strncmp(line, ".SS ", 4))) {
2473                 if(!*line) { *p++ = '\n'; *p++ = '\n'; q = p; continue; }
2474                 if(*line == '.') continue;
2475                 *p++ = ' '; cnt++;
2476                 while(*line) {
2477                     if(*line < ' ') { line++; continue;}
2478                     if(*line == ' ' && p - q > 80) *line = '\n', q = p;
2479                     *p++ = *line++;
2480                 }
2481                 if(p - text > 9900) break;
2482             }
2483             *p = NULLCHAR;
2484             ErrorPopUp(title, text, FALSE);
2485             return;
2486         }
2487     }
2488     snprintf(text, MSG_SIZ, "No help on '%s'\n", buf+3);
2489     DisplayNote(text);
2490 }
2491
2492 void
2493 DisplayHelp (char *name)
2494 {
2495     char buf[MSG_SIZ], tidy[MSG_SIZ];
2496     FILE *f;
2497     if(currentCps) {
2498         TidyProgramName(currentCps == &first ? appData.firstChessProgram : appData.secondChessProgram, "localhost", tidy);
2499         snprintf(buf, MSG_SIZ, "/usr/local/share/man/man6/%s.6", tidy);
2500     } else snprintf(buf, MSG_SIZ, "%s/man6/xboard.6", manDir);
2501     f = fopen(buf, "r");
2502     if(!f && currentCps) { // engine manual could be in two places
2503         snprintf(buf, MSG_SIZ, "/usr/share/man/man6/%s.6", tidy);
2504         f = fopen(buf, "r");
2505     }
2506     if(f) {
2507         GetHelpText(f, name);
2508         fclose(f);
2509     }
2510 }
2511
2512 #define PAUSE_BUTTON "P"
2513 #define PIECE_MENU_SIZE 18
2514 static String pieceMenuStrings[2][PIECE_MENU_SIZE+1] = {
2515     { N_("White"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
2516       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
2517       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
2518       N_("Empty square"), N_("Clear board"), NULL },
2519     { N_("Black"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
2520       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
2521       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
2522       N_("Empty square"), N_("Clear board"), NULL }
2523 };
2524 /* must be in same order as pieceMenuStrings! */
2525 static ChessSquare pieceMenuTranslation[2][PIECE_MENU_SIZE] = {
2526     { WhitePlay, (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2527         WhiteRook, WhiteQueen, WhiteKing, (ChessSquare) 0, WhiteAlfil,
2528         WhiteCannon, WhiteAngel, WhiteMarshall, (ChessSquare) 0,
2529         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2530     { BlackPlay, (ChessSquare) 0, BlackPawn, BlackKnight, BlackBishop,
2531         BlackRook, BlackQueen, BlackKing, (ChessSquare) 0, BlackAlfil,
2532         BlackCannon, BlackAngel, BlackMarshall, (ChessSquare) 0,
2533         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2534 };
2535
2536 #define DROP_MENU_SIZE 6
2537 static String dropMenuStrings[DROP_MENU_SIZE+1] = {
2538     "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"), N_("Queen"), NULL
2539   };
2540 /* must be in same order as dropMenuStrings! */
2541 static ChessSquare dropMenuTranslation[DROP_MENU_SIZE] = {
2542     (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2543     WhiteRook, WhiteQueen
2544 };
2545
2546 // [HGM] experimental code to pop up window just like the main window, using GenercicPopUp
2547
2548 static Option *Exp P((int n, int x, int y));
2549 void MenuCallback P((int n));
2550 void SizeKludge P((int n));
2551 static Option *LogoW P((int n, int x, int y));
2552 static Option *LogoB P((int n, int x, int y));
2553
2554 static int pmFromX = -1, pmFromY = -1;
2555 void *userLogo;
2556
2557 void
2558 DisplayLogos (Option *w1, Option *w2)
2559 {
2560         void *whiteLogo = first.programLogo, *blackLogo = second.programLogo;
2561         if(appData.autoLogo) {
2562           if(appData.noChessProgram) whiteLogo = blackLogo = NULL;
2563           if(appData.icsActive) whiteLogo = blackLogo = second.programLogo;
2564           switch(gameMode) { // pick logos based on game mode
2565             case IcsObserving:
2566                 whiteLogo = second.programLogo; // ICS logo
2567                 blackLogo = second.programLogo;
2568             default:
2569                 break;
2570             case IcsPlayingWhite:
2571                 if(!appData.zippyPlay) whiteLogo = userLogo;
2572                 blackLogo = second.programLogo; // ICS logo
2573                 break;
2574             case IcsPlayingBlack:
2575                 whiteLogo = second.programLogo; // ICS logo
2576                 blackLogo = appData.zippyPlay ? first.programLogo : userLogo;
2577                 break;
2578             case TwoMachinesPlay:
2579                 if(first.twoMachinesColor[0] == 'b') {
2580                     whiteLogo = second.programLogo;
2581                     blackLogo = first.programLogo;
2582                 }
2583                 break;
2584             case MachinePlaysWhite:
2585                 blackLogo = userLogo;
2586                 break;
2587             case MachinePlaysBlack:
2588                 whiteLogo = userLogo;
2589                 blackLogo = first.programLogo;
2590           }
2591         }
2592         DrawLogo(w1, whiteLogo);
2593         DrawLogo(w2, blackLogo);
2594 }
2595
2596 static void
2597 PMSelect (int n)
2598 {   // user callback for board context menus
2599     if (pmFromX < 0 || pmFromY < 0) return;
2600     if(n == W_DROP) DropMenuEvent(dropMenuTranslation[values[n]], pmFromX, pmFromY);
2601     else EditPositionMenuEvent(pieceMenuTranslation[n - W_MENUW][values[n]], pmFromX, pmFromY);
2602 }
2603
2604 static void
2605 CCB (int n)
2606 {
2607     shiftKey = (ShiftKeys() & 3) != 0;
2608     if(n < 0) { // button != 1
2609         n = -n;
2610         if(shiftKey && (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack)) {
2611             AdjustClock(n == W_BLACK, 1);
2612         }
2613     } else
2614     ClockClick(n == W_BLACK);
2615 }
2616
2617 Option mainOptions[] = { // description of main window in terms of generic dialog creator
2618 { 0, 0xCA, 0, NULL, NULL, "", NULL, BarBegin, "" }, // menu bar
2619   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_File") },
2620   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Edit") },
2621   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_View") },
2622   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Mode") },
2623   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Action") },
2624   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("E_ngine") },
2625   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Options") },
2626   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Help") },
2627 { 0, 0, 0, NULL, (void*)&SizeKludge, "", NULL, BarEnd, "" },
2628 { 0, LR|T2T|BORDER|SAME_ROW, 0, NULL, NULL, NULL, NULL, Label, "1" }, // optional title in window
2629 { 50,    LL|TT,            100, NULL, (void*) &LogoW, NULL, NULL, Skip, "" }, // white logo
2630 { 12,   L2L|T2T,           200, NULL, (void*) &CCB, NULL, NULL, Label, "White" }, // white clock
2631 { 13,   R2R|T2T|SAME_ROW,  200, NULL, (void*) &CCB, NULL, NULL, Label, "Black" }, // black clock
2632 { 50,    RR|TT|SAME_ROW,   100, NULL, (void*) &LogoB, NULL, NULL, Skip, "" }, // black logo
2633 { 0, LR|T2T|BORDER,        401, NULL, NULL, "", NULL, Skip, "2" }, // backup for title in window (if no room for other)
2634 { 0, LR|T2T|BORDER,        270, NULL, NULL, NULL, NULL, Label, "message", &appData.font }, // message field
2635 { 0, RR|TT|SAME_ROW,       125, NULL, NULL, "", NULL, BoxBegin, "" }, // (optional) button bar
2636   { 0,    0,     0, NULL, (void*) &ToStartEvent,  NULL, NULL, Button, N_("<<"), &appData.font },
2637   { 0, SAME_ROW, 0, NULL, (void*) &BackwardEvent, NULL, NULL, Button, N_("<"),  &appData.font },
2638   { 0, SAME_ROW, 0, NULL, (void*) &PauseEvent,    NULL, NULL, Button, N_(PAUSE_BUTTON), &appData.font },
2639   { 0, SAME_ROW, 0, NULL, (void*) &ForwardEvent,  NULL, NULL, Button, N_(">"),  &appData.font },
2640   { 0, SAME_ROW, 0, NULL, (void*) &ToEndEvent,    NULL, NULL, Button, N_(">>"), &appData.font },
2641 { 0, 0, 0, NULL, NULL, "", NULL, BoxEnd, "" },
2642 { 401, LR|TB, 401, NULL, (char*) &Exp, NULL, NULL, Graph, "shadow board" }, // board
2643   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[0], PopUp, "menuW" },
2644   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[1], PopUp, "menuB" },
2645   { -1, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, dropMenuStrings, PopUp, "menuD" },
2646 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2647 };
2648
2649 Option *
2650 LogoW (int n, int x, int y)
2651 {
2652     if(n == 10) DisplayLogos(&mainOptions[W_WHITE-1], NULL);
2653     return NULL;
2654 }
2655
2656 Option *
2657 LogoB (int n, int x, int y)
2658 {
2659     if(n == 10) DisplayLogos(NULL, &mainOptions[W_BLACK+1]);
2660     return NULL;
2661 }
2662
2663 void
2664 SizeKludge (int n)
2665 {   // callback called by GenericPopUp immediately after sizing the menu bar
2666     int width = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2667     int w = width - 44 - mainOptions[n].min;
2668     mainOptions[W_TITLE].max = w; // width left behind menu bar
2669     if(w < 0.4*width) // if no reasonable amount of space for title, force small layout
2670         mainOptions[W_SMALL].type = mainOptions[W_TITLE].type, mainOptions[W_TITLE].type = Skip;
2671 }
2672
2673 void
2674 MenuCallback (int n)
2675 {
2676     MenuProc *proc = (MenuProc *) (((MenuItem*)(mainOptions[n].choice))[values[n]].proc);
2677
2678     if(!proc) RecentEngineEvent(values[n] - firstEngineItem); else (proc)();
2679 }
2680
2681 static Option *
2682 Exp (int n, int x, int y)
2683 {
2684     static int but1, but3, oldW, oldH, oldX, oldY;
2685     int menuNr = -3, sizing, f, r;
2686     TimeMark now;
2687     extern Boolean right;
2688
2689     if(right) {  // kludgy way to let button 1 double as button 3 when back-end requests this
2690         if(but1 && n == 0) but1 = 0, but3 = 1;
2691         else if(n == -1) n = -3, right = FALSE;
2692     }
2693
2694     if(n == 0) { // motion
2695         oldX = x; oldY = y;
2696         if(SeekGraphClick(Press, x, y, 1)) return NULL;
2697         if((but1 || dragging == 2) && !PromoScroll(x, y)) DragPieceMove(x, y);
2698         if(but3) MovePV(x, y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
2699         if(appData.highlightDragging) {
2700             f = EventToSquare(x, BOARD_WIDTH);  if ( flipView && f >= 0) f = BOARD_WIDTH - 1 - f;
2701             r = EventToSquare(y, BOARD_HEIGHT); if (!flipView && r >= 0) r = BOARD_HEIGHT - 1 - r;
2702             HoverEvent(x, y, f, r);
2703         }
2704         return NULL;
2705     }
2706     if(n != 10 && PopDown(PromoDlg)) fromX = fromY = -1; // user starts fiddling with board when promotion dialog is up
2707     else GetTimeMark(&now);
2708     shiftKey = ShiftKeys();
2709     controlKey = (shiftKey & 0xC) != 0;
2710     shiftKey = (shiftKey & 3) != 0;
2711     switch(n) {
2712         case  1: LeftClick(Press,   x, y), but1 = 1; break;
2713         case -1: LeftClick(Release, x, y), but1 = 0; break;
2714         case  2: shiftKey = !shiftKey;
2715         case  3: menuNr = RightClick(Press,   x, y, &pmFromX, &pmFromY), but3 = 1; break;
2716         case -2: shiftKey = !shiftKey;
2717         case -3: menuNr = RightClick(Release, x, y, &pmFromX, &pmFromY), but3 = 0; break;
2718         case  4: Wheel(-1, oldX, oldY); break;
2719         case  5: Wheel(1, oldX, oldY); break;
2720         case 10:
2721             sizing = (oldW != x || oldH != y);
2722             oldW = x; oldH = y;
2723             InitDrawingHandle(mainOptions + W_BOARD);
2724             if(sizing && SubtractTimeMarks(&now, &programStartTime) > 10000) return NULL; // don't redraw while sizing (except at startup)
2725             DrawPosition(True, NULL);
2726         default:
2727             return NULL;
2728     }
2729
2730     switch(menuNr) {
2731       case 0: return &mainOptions[shiftKey ? W_MENUW: W_MENUB];
2732       case 1: SetupDropMenu(); return &mainOptions[W_DROP];
2733       case 2:
2734       case -1: ErrorPopDown();
2735       case -2:
2736       default: break; // -3, so no clicks caught
2737     }
2738     return NULL;
2739 }
2740
2741 Option *
2742 BoardPopUp (int squareSize, int lineGap, void *clockFontThingy)
2743 {
2744     int i, size = BOARD_WIDTH*(squareSize + lineGap) + lineGap, logo = appData.logoSize;
2745     int f = 2*appData.fixedSize; // width fudge, needed for unknown reasons to not clip board
2746     mainOptions[W_WHITE].choice = (char**) clockFontThingy;
2747     mainOptions[W_BLACK].choice = (char**) clockFontThingy;
2748     mainOptions[W_BOARD].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2749     mainOptions[W_BOARD].max = mainOptions[W_SMALL].max = size; // board size
2750     mainOptions[W_SMALL].max = size - 2; // board title (subtract border!)
2751     mainOptions[W_BLACK].max = mainOptions[W_WHITE].max = size/2-3; // clock width
2752     mainOptions[W_MESSG].max = appData.showButtonBar ? size-135+f : size-2+f; // message
2753     mainOptions[W_MENU].max = size-40; // menu bar
2754     mainOptions[W_TITLE].type = appData.titleInWindow ? Label : Skip ;
2755     if(logo && logo <= size/4) { // Activate logos
2756         mainOptions[W_WHITE-1].type = mainOptions[W_BLACK+1].type = Graph;
2757         mainOptions[W_WHITE-1].max  = mainOptions[W_BLACK+1].max  = logo;
2758         mainOptions[W_WHITE-1].value= mainOptions[W_BLACK+1].value= logo/2;
2759         mainOptions[W_WHITE].min  |= SAME_ROW;
2760         mainOptions[W_WHITE].max  = mainOptions[W_BLACK].max  -= logo + 4;
2761         mainOptions[W_WHITE].name = mainOptions[W_BLACK].name = "Double\nHeight";
2762     }
2763     if(!appData.showButtonBar) for(i=W_BUTTON; i<W_BOARD; i++) mainOptions[i].type = Skip;
2764     for(i=0; i<8; i++) mainOptions[i+1].choice = (char**) menuBar[i].mi;
2765     AppendEnginesToMenu(appData.recentEngineList);
2766     GenericPopUp(mainOptions, "XBoard", BoardWindow, BoardWindow, NONMODAL, 1); // allways top-level
2767     return mainOptions;
2768 }
2769
2770 static Option *
2771 SlaveExp (int n, int x, int y)
2772 {
2773     if(n == 10) { // expose event
2774         flipView = !flipView; partnerUp = !partnerUp;
2775         DrawPosition(True, NULL); // [HGM] dual: draw other board in other orientation
2776         flipView = !flipView; partnerUp = !partnerUp;
2777     }
2778     return NULL;
2779 }
2780
2781 Option dualOptions[] = { // auxiliary board window
2782 { 0, L2L|T2T,              198, NULL, NULL, NULL, NULL, Label, "White" }, // white clock
2783 { 0, R2R|T2T|SAME_ROW,     198, NULL, NULL, NULL, NULL, Label, "Black" }, // black clock
2784 { 0, LR|T2T|BORDER,        401, NULL, NULL, NULL, NULL, Label, "This feature is experimental" }, // message field
2785 { 401, LR|TT, 401, NULL, (char*) &SlaveExp, NULL, NULL, Graph, "shadow board" }, // board
2786 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2787 };
2788
2789 void
2790 SlavePopUp ()
2791 {
2792     int size = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2793     // copy params from main board
2794     dualOptions[0].choice = mainOptions[W_WHITE].choice;
2795     dualOptions[1].choice = mainOptions[W_BLACK].choice;
2796     dualOptions[3].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2797     dualOptions[3].max = dualOptions[2].max = size; // board width
2798     dualOptions[0].max = dualOptions[1].max = size/2 - 3; // clock width
2799     GenericPopUp(dualOptions, "XBoard", DummyDlg, BoardWindow, NONMODAL, appData.topLevel);
2800     SlaveResize(dualOptions+3);
2801 }
2802
2803 void
2804 DisplayWhiteClock (long timeRemaining, int highlight)
2805 {
2806     if(appData.noGUI) return;
2807     if(twoBoards && partnerUp) {
2808         DisplayTimerLabel(&dualOptions[0], _("White"), timeRemaining, highlight);
2809         return;
2810     }
2811     DisplayTimerLabel(&mainOptions[W_WHITE], _("White"), timeRemaining, highlight);
2812     if(highlight) SetClockIcon(0);
2813 }
2814
2815 void
2816 DisplayBlackClock (long timeRemaining, int highlight)
2817 {
2818     if(appData.noGUI) return;
2819     if(twoBoards && partnerUp) {
2820         DisplayTimerLabel(&dualOptions[1], _("Black"), timeRemaining, highlight);
2821         return;
2822     }
2823     DisplayTimerLabel(&mainOptions[W_BLACK], _("Black"), timeRemaining, highlight);
2824     if(highlight) SetClockIcon(1);
2825 }
2826
2827
2828 //---------------------------------------------
2829
2830 void
2831 DisplayMessage (char *message, char *extMessage)
2832 {
2833   /* display a message in the message widget */
2834
2835   char buf[MSG_SIZ];
2836
2837   if (extMessage)
2838     {
2839       if (*message)
2840         {
2841           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
2842           message = buf;
2843         }
2844       else
2845         {
2846           message = extMessage;
2847         };
2848     };
2849
2850     safeStrCpy(lastMsg, message, MSG_SIZ); // [HGM] make available
2851
2852   /* need to test if messageWidget already exists, since this function
2853      can also be called during the startup, if for example a Xresource
2854      is not set up correctly */
2855   if(mainOptions[W_MESSG].handle)
2856     SetWidgetLabel(&mainOptions[W_MESSG], message);
2857
2858   return;
2859 }
2860
2861 //----------------------------------- File Browser -------------------------------
2862
2863 #ifdef HAVE_DIRENT_H
2864 #include <dirent.h>
2865 #else
2866 #include <sys/dir.h>
2867 #define dirent direct
2868 #endif
2869
2870 #include <sys/stat.h>
2871
2872 #define MAXFILES 1000
2873
2874 static DialogClass savDlg;
2875 static ChessProgramState *savCps;
2876 static FILE **savFP;
2877 static char *fileName, *extFilter, *savMode, **namePtr;
2878 static int folderPtr, filePtr, oldVal, byExtension, extFlag, pageStart, cnt;
2879 static char curDir[MSG_SIZ], title[MSG_SIZ], *folderList[MAXFILES], *fileList[MAXFILES];
2880
2881 static char *FileTypes[] = {
2882 "Chess Games",
2883 "Chess Positions",
2884 "Tournaments",
2885 "Opening Books",
2886 "Sound files",
2887 "Images",
2888 "Settings (*.ini)",
2889 "Log files",
2890 "All files",
2891 NULL,
2892 "PGN",
2893 "Old-Style Games",
2894 "FEN",
2895 "Old-Style Positions",
2896 NULL,
2897 NULL
2898 };
2899
2900 static char *Extensions[] = {
2901 ".pgn .game",
2902 ".fen .epd .pos",
2903 ".trn",
2904 ".bin",
2905 ".wav",
2906 ".png",
2907 ".ini",
2908 ".log",
2909 "",
2910 "INVALID",
2911 ".pgn",
2912 ".game",
2913 ".fen",
2914 ".pos",
2915 NULL,
2916 ""
2917 };
2918
2919 void DirSelProc P((int n, int sel));
2920 void FileSelProc P((int n, int sel));
2921 void SetTypeFilter P((int n));
2922 int BrowseOK P((int n));
2923 void Switch P((int n));
2924 void CreateDir P((int n));
2925
2926 Option browseOptions[] = {
2927 {   0,    LR|T2T,      500, NULL, NULL, NULL, NULL, Label, title },
2928 {   0,    L2L|T2T,     250, NULL, NULL, NULL, NULL, Label, N_("Directories:") },
2929 {   0,R2R|T2T|SAME_ROW,100, NULL, NULL, NULL, NULL, Label, N_("Files:") },
2930 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by name") },
2931 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by type") },
2932 { 300,    L2L|TB,      250, NULL, (void*) folderList, (char*) &DirSelProc, NULL, ListBox, "" },
2933 { 300, R2R|TB|SAME_ROW,250, NULL, (void*) fileList, (char*) &FileSelProc, NULL, ListBox, "" },
2934 {   0,       0,        300, NULL, (void*) &fileName, NULL, NULL, TextBox, N_("Filename:") },
2935 {   0,    SAME_ROW,    120, NULL, (void*) &CreateDir, NULL, NULL, Button, N_("New directory") },
2936 {   0, COMBO_CALLBACK, 150, NULL, (void*) &SetTypeFilter, NULL, FileTypes, ComboBox, N_("File type:") },
2937 {   0,    SAME_ROW,      0, NULL, (void*) &BrowseOK, "", NULL, EndMark , "" }
2938 };
2939
2940 int
2941 BrowseOK (int n)
2942 {
2943         if(!fileName[0]) { // it is enough to have a file selected
2944             if(browseOptions[6].textValue) { // kludge: if callback specified we browse for file
2945                 int sel = SelectedListBoxItem(&browseOptions[6]);
2946                 if(sel < 0 || sel >= filePtr) return FALSE;
2947                 ASSIGN(fileName, fileList[sel]);
2948             } else { // we browse for path
2949                 ASSIGN(fileName, curDir); // kludge: without callback we browse for path
2950             }
2951         }
2952         if(!fileName[0]) return FALSE; // refuse OK when no file
2953         if(!savMode[0]) { // browsing for name only (dialog Browse button)
2954                 if(fileName[0] == '/') // We already had a path name
2955                     snprintf(title, MSG_SIZ, "%s", fileName);
2956                 else
2957                     snprintf(title, MSG_SIZ, "%s/%s", curDir, fileName);
2958                 SetWidgetText((Option*) savFP, title, savDlg);
2959                 currentCps = savCps; // could return to Engine Settings dialog!
2960                 return TRUE;
2961         }
2962         *savFP = fopen(fileName, savMode);
2963         if(*savFP == NULL) return FALSE; // refuse OK if file not openable
2964         ASSIGN(*namePtr, fileName);
2965         ScheduleDelayedEvent(DelayedLoad, 50);
2966         currentCps = savCps; // not sure this is ever non-null
2967         return TRUE;
2968 }
2969
2970 int
2971 AlphaNumCompare (char *p, char *q)
2972 {
2973     while(*p) {
2974         if(isdigit(*p) && isdigit(*q) && atoi(p) != atoi(q))
2975              return (atoi(p) > atoi(q) ? 1 : -1);
2976         if(*p != *q) break;
2977         p++, q++;
2978     }
2979     if(*p == *q) return 0;
2980     return (*p > *q ? 1 : -1);
2981 }
2982
2983 int
2984 Comp (const void *s, const void *t)
2985 {
2986     char *p = *(char**) s, *q = *(char**) t;
2987     if(extFlag) {
2988         char *h; int r;
2989         while(h = strchr(p, '.')) p = h+1;
2990         if(p == *(char**) s) p = "";
2991         while(h = strchr(q, '.')) q = h+1;
2992         if(q == *(char**) t) q = "";
2993         r = AlphaNumCompare(p, q);
2994         if(r) return r;
2995     }
2996     return AlphaNumCompare( *(char**) s, *(char**) t );
2997 }
2998
2999 void
3000 ListDir (int pathFlag)
3001 {
3002         DIR *dir;
3003         struct dirent *dp;
3004         struct stat statBuf;
3005         static int lastFlag;
3006
3007         if(pathFlag < 0) pathFlag = lastFlag;
3008         lastFlag = pathFlag;
3009         dir = opendir(".");
3010         getcwd(curDir, MSG_SIZ);
3011         snprintf(title, MSG_SIZ, "%s   %s", _("Contents of"), curDir);
3012         folderPtr = filePtr = cnt = 0; // clear listing
3013
3014         while (dp = readdir(dir)) { // pass 1: list foders
3015             char *s = dp->d_name;
3016             if(!stat(s, &statBuf) && S_ISDIR(statBuf.st_mode)) { // stat succeeds and tells us it is directory
3017                 if(s[0] == '.' && strcmp(s, "..")) continue; // suppress hidden, except ".."
3018                 ASSIGN(folderList[folderPtr], s); if(folderPtr < MAXFILES-2) folderPtr++;
3019             } else if(!pathFlag) {
3020                 char *s = dp->d_name, match=0;
3021 //              if(cnt == pageStart) { ASSIGN }
3022                 if(s[0] == '.') continue; // suppress hidden files
3023                 if(extFilter[0]) { // [HGM] filter on extension
3024                     char *p = extFilter, *q;
3025                     do {
3026                         if(q = strchr(p, ' ')) *q = 0;
3027                         if(strstr(s, p)) match++;
3028                         if(q) *q = ' ';
3029                     } while(q && (p = q+1));
3030                     if(!match) continue;
3031                 }
3032                 if(filePtr == MAXFILES-2) continue;
3033                 if(cnt++ < pageStart) continue;
3034                 ASSIGN(fileList[filePtr], s); filePtr++;
3035             }
3036         }
3037         if(filePtr == MAXFILES-2) { ASSIGN(fileList[filePtr], _("  next page")); filePtr++; }
3038         FREE(folderList[folderPtr]); folderList[folderPtr] = NULL;
3039         FREE(fileList[filePtr]); fileList[filePtr] = NULL;
3040         closedir(dir);
3041         extFlag = 0;         qsort((void*)folderList, folderPtr, sizeof(char*), &Comp);
3042         extFlag = byExtension; qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
3043 }
3044
3045 void
3046 Refresh (int pathFlag)
3047 {
3048     ListDir(pathFlag); // and make new one
3049     LoadListBox(&browseOptions[5], "", -1, -1);
3050     LoadListBox(&browseOptions[6], "", -1, -1);
3051     SetWidgetLabel(&browseOptions[0], title);
3052 }
3053
3054 static char msg1[] = N_("FIRST TYPE DIRECTORY NAME HERE");
3055 static char msg2[] = N_("TRY ANOTHER NAME");
3056
3057 void
3058 CreateDir (int n)
3059 {
3060     char *name, *errmsg = "";
3061     GetWidgetText(&browseOptions[n-1], &name);
3062     if(!strcmp(name, msg1) || !strcmp(name, msg2)) return;
3063     if(!name[0]) errmsg = _(msg1); else
3064     if(mkdir(name, 0755)) errmsg = _(msg2);
3065     else {
3066         chdir(name);
3067         Refresh(-1);
3068     }
3069     SetWidgetText(&browseOptions[n-1], errmsg, BrowserDlg);
3070 }
3071
3072 void
3073 Switch (int n)
3074 {
3075     if(byExtension == (n == 4)) return;
3076     extFlag = byExtension = (n == 4);
3077     qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
3078     LoadListBox(&browseOptions[6], "", -1, -1);
3079 }
3080
3081 void
3082 SetTypeFilter (int n)
3083 {
3084     int j = values[n];
3085     if(j == browseOptions[n].value) return; // no change
3086     browseOptions[n].value = j;
3087     SetWidgetLabel(&browseOptions[n], FileTypes[j]);
3088     ASSIGN(extFilter, Extensions[j]);
3089     pageStart = 0;
3090     Refresh(-1); // uses pathflag remembered by ListDir
3091     values[n] = oldVal; // do not disturb combo settings of underlying dialog
3092 }
3093
3094 void
3095 FileSelProc (int n, int sel)
3096 {
3097     if(sel < 0 || fileList[sel] == NULL) return;
3098     if(sel == MAXFILES-2) { pageStart = cnt; Refresh(-1); return; }
3099     ASSIGN(fileName, fileList[sel]);
3100     if(BrowseOK(0)) PopDown(BrowserDlg);
3101 }
3102
3103 void
3104 DirSelProc (int n, int sel)
3105 {
3106     if(!chdir(folderList[sel])) { // cd succeeded, so we are in new directory now
3107         Refresh(-1);
3108     }
3109 }
3110
3111 void
3112 StartDir (char *filter, char *newName)
3113 {
3114     static char *gamesDir, *trnDir, *imgDir, *bookDir;
3115     static char curDir[MSG_SIZ];
3116     char **res = NULL;
3117     if(!filter || !*filter) return;
3118     if(strstr(filter, "pgn")) res = &gamesDir; else
3119     if(strstr(filter, "bin")) res = &bookDir; else
3120     if(strstr(filter, "png")) res = &imgDir; else
3121     if(strstr(filter, "trn")) res = &trnDir; else
3122     if(strstr(filter, "fen")) res = &appData.positionDir;
3123     if(res) {
3124         if(newName) {
3125             char *p, *q;
3126             if(*newName) {
3127                 ASSIGN(*res, newName);
3128                 for(p=*res; q=strchr(p, '/');) p = q + 1; *p = NULLCHAR;
3129             }
3130             if(*curDir) chdir(curDir);
3131             *curDir = NULLCHAR;
3132         } else {
3133             getcwd(curDir, MSG_SIZ);
3134             if(*res && **res) chdir(*res);
3135         }
3136     }
3137 }
3138
3139 void
3140 Browse (DialogClass dlg, char *label, char *proposed, char *ext, Boolean pathFlag, char *mode, char **name, FILE **fp)
3141 {
3142     int j=0;
3143     savFP = fp; savMode = mode, namePtr = name, savCps = currentCps, oldVal = values[9], savDlg = dlg; // save params, for use in callback
3144     ASSIGN(extFilter, ext);
3145     ASSIGN(fileName, proposed ? proposed : "");
3146     for(j=0; Extensions[j]; j++) // look up actual value in list of possible values, to get selection nr
3147         if(extFilter && !strcmp(extFilter, Extensions[j])) break;
3148     if(Extensions[j] == NULL) { j++; ASSIGN(FileTypes[j], extFilter); }
3149     browseOptions[9].value = j;
3150     browseOptions[6].textValue = (char*) (pathFlag ? NULL : &FileSelProc); // disable file listbox during path browsing
3151     pageStart = 0; ListDir(pathFlag);
3152     currentCps = NULL;
3153     GenericPopUp(browseOptions, label, BrowserDlg, dlg, MODAL, 0);
3154     SetWidgetLabel(&browseOptions[9], FileTypes[j]);
3155 }
3156
3157 static char *openName;
3158 FileProc fileProc;
3159 char *fileOpenMode;
3160 FILE *openFP;
3161
3162 void
3163 DelayedLoad ()
3164 {
3165   (void) (*fileProc)(openFP, 0, openName);
3166 }
3167
3168 void
3169 FileNamePopUp (char *label, char *def, char *filter, FileProc proc, char *openMode)
3170 {
3171     fileProc = proc;            /* I can't see a way not */
3172     fileOpenMode = openMode;    /*   to use globals here */
3173     FileNamePopUpWrapper(label, def, filter, proc, False, openMode, &openName, &openFP);
3174 }
3175
3176 void
3177 ActivateTheme (int col)
3178 {
3179     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap; else lineGap = defaultLineGap;
3180     InitDrawingParams(strcmp(oldPieceDir, appData.pieceDirectory));
3181     InitDrawingSizes(-1, 0);
3182     DrawPosition(True, NULL);
3183 }
3184