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