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