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