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