Let file chooser show preview of textures on board
[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, 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, 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.darkBackTextureFile, ".png", (char**)(intptr_t) 1, FileName, N_("Dark-Squares Texture File:") },
945 { 0, 0, 0, NULL, (void*) &appData.liteBackTextureFile, ".png", (char**)(intptr_t) 2, FileName, N_("Light-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, "",  (char**)(intptr_t) 3, 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 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 NewFont (int n, int fnr, char *font)
1631 {   // figure out if font changed, and if so, store it in the fonts table as a side effect
1632     if(!strcmp(oldFont[n], font)) return 0; // not changed
1633     ASSIGN(fontTable[fnr][initialSquareSize], font);
1634     fontIsSet[fnr] = fontValid[fnr][initialSquareSize] = True;
1635     return 1; // changed
1636 }
1637
1638 static int
1639 FontsOK (int n)
1640 {
1641     int i;
1642     PopDown(TransientDlg); // Early popdown to prevent expose events frommasking each other
1643     LockBoardSize(0);
1644     if(NewFont(0, CLOCK_FONT,   appData.clockFont)) DisplayBothClocks();
1645     if(NewFont(1, MESSAGE_FONT, appData.font)) {
1646         ApplyFont(&mainOptions[W_MESSG], NULL);
1647         for(i=1; i<6; i++) ApplyFont(&mainOptions[W_BUTTON+i], NULL);
1648     }
1649     LockBoardSize(1); // unlock
1650     if(NewFont(3, EDITTAGS_FONT,    appData.tagsFont))    ApplyFont(&tagsOptions[1], NULL);
1651     if(NewFont(4, COMMENT_FONT,     appData.commentFont)) ApplyFont(&commentOptions[0], NULL);
1652     if(NewFont(5, MOVEHISTORY_FONT, appData.historyFont)) {
1653         ApplyFont(&historyOptions[0], NULL);
1654         ApplyFont(&engoutOptions[5], NULL);
1655         ApplyFont(&engoutOptions[12], NULL);
1656     }
1657     if(NewFont(6, GAMELIST_FONT, appData.gameListFont)) ApplyFont(&gamesOptions[0], NULL);
1658     if(NewFont(2, CONSOLE_FONT,  appData.icsFont)) {
1659         ApplyFont(&chatOptions[11], appData.icsFont);
1660         AppendColorized(&chatOptions[6], NULL, 0); // kludge to replace font tag
1661     }
1662     DrawPosition(TRUE, NULL); // for coord font
1663     return 0; // suppress normal popdown because already done
1664 }
1665
1666 static Option fontOptions[] = {
1667   { 0,        60, 200, NULL, (void*) &appData.clockFont, NULL, NULL, TextBox, N_("Clocks (requires restart):") },
1668   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1669   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1670   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1671   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1672   {  666, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("*") },
1673   { 0,         60, 70, NULL, (void*) &appData.font, NULL, NULL, TextBox, N_("Message (above board):") },
1674   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1675   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1676   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1677   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1678   {  666, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("*") },
1679   { 0,         60, 70, NULL, (void*) &appData.icsFont, NULL, NULL, TextBox, N_("ICS Chat/Console:") },
1680   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1681   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1682   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1683   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1684   {  666, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("*") },
1685   { 0,         60, 70, NULL, (void*) &appData.tagsFont, NULL, NULL, TextBox, N_("Edit tags / book / engine list:") },
1686   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1687   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1688   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1689   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1690   {  666, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("*") },
1691   { 0,         60, 70, NULL, (void*) &appData.commentFont, NULL, NULL, TextBox, N_("Edit comments:") },
1692   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1693   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1694   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1695   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1696   {  666, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("*") },
1697   { 0,         60, 70, NULL, (void*) &appData.historyFont, NULL, NULL, TextBox, N_("Move history / Engine Output:") },
1698   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1699   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1700   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1701   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1702   {  666, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("*") },
1703   { 0,         60, 70, NULL, (void*) &appData.gameListFont, NULL, NULL, TextBox, N_("Game list:") },
1704   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1705   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1706   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1707   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1708   {  666, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("*") },
1709   {   0,  0,    0, NULL, NULL, NULL, NULL, Label, N_("\nThe * buttons will set the font to the one selected below:") },
1710   {   0,  0,    0, NULL, NULL, NULL, NULL, Button, "fontsel" },
1711   { 0, 0, 0, NULL, (void*) &FontsOK, "", NULL, EndMark , "" }
1712 };
1713
1714 static char name[MSG_SIZ], *bold, *ital, points;
1715
1716 static void
1717 BreakUp (char *font)
1718 {
1719     char *p = name, *norm;
1720     safeStrCpy(name, font, MSG_SIZ);
1721     bold = StrCaseStr(name, "bold");
1722     ital = StrCaseStr(name, "ital");
1723     norm = StrCaseStr(name, "normal");
1724     points = 0;
1725     while(p && *p && !(points = atoi(p))) p = strchr(p+1, ' ');
1726     if(points) p[*p == ' '] = 0;
1727     if(bold) *bold = 0;
1728     if(ital) *ital = 0;
1729     if(norm) *norm = 0;
1730 }
1731
1732 static void
1733 Collect ()
1734 {
1735     if(bold) strcat(name, "Bold ");
1736     if(ital) strcat(name, "Italic ");
1737     if(!ital && !bold && strlen(name) < 2) strncpy(name, "Normal ", MSG_SIZ);
1738     if(points) sprintf(name + strlen(name), "%d", points); else strcat(name, "%d");
1739 }
1740
1741 static void
1742 AdjustFont (int n)
1743 {
1744     int button = fontOptions[n].value, base = n - button;
1745     char *oldFont;
1746     GetWidgetText(&fontOptions[base], &oldFont);
1747     BreakUp(oldFont); // take apart old font name
1748     switch(button) {
1749       case 1: points++; break;
1750       case 2: points--; break;
1751       case 3: if(bold) bold = NULL; else bold = name; break;
1752       case 4: if(ital) ital = NULL; else ital = name; break;
1753     }
1754     Collect();
1755     SetWidgetText(&fontOptions[base], name, TransientDlg);
1756     ApplyFont(&fontOptions[base], name);
1757 }
1758
1759 void
1760 FontsProc ()
1761 {
1762     int i;
1763     if(strstr(appData.font, "-*-")) { DisplayNote(_("This only works in the GTK build")); return; }
1764     GenericPopUp(fontOptions, _("Fonts"), TransientDlg, BoardWindow, MODAL, 0);
1765     for(i=0; i<7; i++) {
1766         ApplyFont(&fontOptions[6*i], *(char**)fontOptions[6*i].target);
1767         ASSIGN(oldFont[i], *(char**)fontOptions[6*i].target);
1768     }
1769 }
1770
1771 //------------------------------------------------------ Time Control -----------------------------------
1772
1773 static int TcOK P((int n));
1774 int tmpMoves, tmpTc, tmpInc, tmpOdds1, tmpOdds2, tcType, by60;
1775
1776 static void SetTcType P((int n));
1777
1778 static char *
1779 Value (int n)
1780 {
1781         static char buf[MSG_SIZ];
1782         snprintf(buf, MSG_SIZ, "%d", n);
1783         return buf;
1784 }
1785
1786 static Option tcOptions[] = {
1787 {   0,  0,    0, NULL, (void*) &SetTcType, NULL, NULL, Button, N_("classical") },
1788 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("incremental") },
1789 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("fixed max") },
1790 {   0,  0,    0, NULL, (void*) &by60,     "",  NULL, CheckBox, N_("Divide entered times by 60") },
1791 {   0,  0,  200, NULL, (void*) &tmpMoves, NULL, NULL, Spin, N_("Moves per session:") },
1792 {   0,  0,10000, NULL, (void*) &tmpTc,    NULL, NULL, Spin, N_("Initial time (min):") },
1793 {   0, 0, 10000, NULL, (void*) &tmpInc,   NULL, NULL, Spin, N_("Increment or max (sec/move):") },
1794 {   0,  0,    0, NULL, NULL, NULL, NULL, Label, N_("Time-Odds factors:") },
1795 {   0,  1, 1000, NULL, (void*) &tmpOdds1, NULL, NULL, Spin, N_("Engine #1") },
1796 {   0,  1, 1000, NULL, (void*) &tmpOdds2, NULL, NULL, Spin, N_("Engine #2 / Human") },
1797 {   0,  0,    0, NULL, (void*) &TcOK, "", NULL, EndMark , "" }
1798 };
1799
1800 static int
1801 TcOK (int n)
1802 {
1803     char *tc, buf[MSG_SIZ];
1804     if(tcType == 0 && tmpMoves <= 0) return 0;
1805     if(tcType == 2 && tmpInc <= 0) return 0;
1806     GetWidgetText(&tcOptions[5], &tc); // get original text, in case it is min:sec
1807     if(by60) snprintf(buf, MSG_SIZ, "%d:%02d", tmpTc/60, tmpTc%60), tc=buf;
1808     searchTime = 0;
1809     switch(tcType) {
1810       case 0:
1811         if(!ParseTimeControl(tc, -1, tmpMoves)) return 0;
1812         appData.movesPerSession = tmpMoves;
1813         ASSIGN(appData.timeControl, tc);
1814         appData.timeIncrement = -1;
1815         break;
1816       case 1:
1817         if(!ParseTimeControl(tc, tmpInc, 0)) return 0;
1818         ASSIGN(appData.timeControl, tc);
1819         appData.timeIncrement = (by60 ? tmpInc/60. : tmpInc);
1820         break;
1821       case 2:
1822         searchTime = (by60 ? tmpInc/60 : tmpInc);
1823     }
1824     appData.firstTimeOdds = first.timeOdds = tmpOdds1;
1825     appData.secondTimeOdds = second.timeOdds = tmpOdds2;
1826     Reset(True, True);
1827     return 1;
1828 }
1829
1830 static void
1831 SetTcType (int n)
1832 {
1833     switch(tcType = n) {
1834       case 0:
1835         SetWidgetText(&tcOptions[4], Value(tmpMoves), TransientDlg);
1836         SetWidgetText(&tcOptions[5], Value(tmpTc), TransientDlg);
1837         SetWidgetText(&tcOptions[6], _("Unused"), TransientDlg);
1838         break;
1839       case 1:
1840         SetWidgetText(&tcOptions[4], _("Unused"), TransientDlg);
1841         SetWidgetText(&tcOptions[5], Value(tmpTc), TransientDlg);
1842         SetWidgetText(&tcOptions[6], Value(tmpInc), TransientDlg);
1843         break;
1844       case 2:
1845         SetWidgetText(&tcOptions[4], _("Unused"), TransientDlg);
1846         SetWidgetText(&tcOptions[5], _("Unused"), TransientDlg);
1847         SetWidgetText(&tcOptions[6], Value(tmpInc), TransientDlg);
1848     }
1849 }
1850
1851 void
1852 TimeControlProc ()
1853 {
1854    if(gameMode != BeginningOfGame) {
1855         DisplayError(_("Changing time control during a game is not implemented"), 0);
1856         return;
1857    }
1858    tmpMoves = appData.movesPerSession;
1859    tmpInc = appData.timeIncrement; if(tmpInc < 0) tmpInc = 0;
1860    tmpOdds1 = tmpOdds2 = 1; tcType = 0;
1861    tmpTc = atoi(appData.timeControl);
1862    by60 = 0;
1863    GenericPopUp(tcOptions, _("Time Control"), TransientDlg, BoardWindow, MODAL, 0);
1864    SetTcType(searchTime ? 2 : appData.timeIncrement < 0 ? 0 : 1);
1865 }
1866
1867 //------------------------------- Ask Question -----------------------------------------
1868
1869 int SendReply P((int n));
1870 char pendingReplyPrefix[MSG_SIZ];
1871 ProcRef pendingReplyPR;
1872 char *answer;
1873
1874 Option askOptions[] = {
1875 { 0, 0, 0, NULL, NULL, NULL, NULL, Label,  NULL },
1876 { 0, 0, 0, NULL, (void*) &answer, "", NULL, TextBox, "" },
1877 { 0, 0, 0, NULL, (void*) &SendReply, "", NULL, EndMark , "" }
1878 };
1879
1880 int
1881 SendReply (int n)
1882 {
1883     char buf[MSG_SIZ];
1884     int err;
1885     char *reply=answer;
1886 //    GetWidgetText(&askOptions[1], &reply);
1887     safeStrCpy(buf, pendingReplyPrefix, sizeof(buf)/sizeof(buf[0]) );
1888     if (*buf) strncat(buf, " ", MSG_SIZ - strlen(buf) - 1);
1889     strncat(buf, reply, MSG_SIZ - strlen(buf) - 1);
1890     strncat(buf, "\n",  MSG_SIZ - strlen(buf) - 1);
1891     OutputToProcess(pendingReplyPR, buf, strlen(buf), &err); // does not go into debug file??? => bug
1892     if (err) DisplayFatalError(_("Error writing to chess program"), err, 0);
1893     return TRUE;
1894 }
1895
1896 void
1897 AskQuestion (char *title, char *question, char *replyPrefix, ProcRef pr)
1898 {
1899     safeStrCpy(pendingReplyPrefix, replyPrefix, sizeof(pendingReplyPrefix)/sizeof(pendingReplyPrefix[0]) );
1900     pendingReplyPR = pr;
1901     ASSIGN(answer, "");
1902     askOptions[0].name = question;
1903     if(GenericPopUp(askOptions, title, AskDlg, BoardWindow, MODAL, 0))
1904         AddHandler(&askOptions[1], AskDlg, 2);
1905 }
1906
1907 //---------------------------- Promotion Popup --------------------------------------
1908
1909 static int count;
1910
1911 static void PromoPick P((int n));
1912
1913 static Option promoOptions[] = {
1914 {   0,         0,    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,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1918 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1919 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1920 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1921 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1922 {   0, SAME_ROW | NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
1923 };
1924
1925 static void
1926 PromoPick (int n)
1927 {
1928     int promoChar = promoOptions[n+count].value;
1929
1930     PopDown(PromoDlg);
1931
1932     if (promoChar == 0) fromX = -1;
1933     if (fromX == -1) return;
1934
1935     if (! promoChar) {
1936         fromX = fromY = -1;
1937         ClearHighlights();
1938         return;
1939     }
1940     if(promoChar == '=' && !IS_SHOGI(gameInfo.variant)) promoChar = NULLCHAR;
1941     UserMoveEvent(fromX, fromY, toX, toY, promoChar);
1942
1943     if (!appData.highlightLastMove || gotPremove) ClearHighlights();
1944     if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
1945     fromX = fromY = -1;
1946 }
1947
1948 static void
1949 SetPromo (char *name, int nr, char promoChar)
1950 {
1951     ASSIGN(promoOptions[nr].name, name);
1952     promoOptions[nr].value = promoChar;
1953     promoOptions[nr].min = SAME_ROW;
1954 }
1955
1956 void
1957 PromotionPopUp (char choice)
1958 { // choice depends on variant: prepare dialog acordingly
1959   count = 8;
1960   SetPromo(_("Cancel"), --count, -1); // Beware: GenericPopUp cannot handle user buttons named "cancel" (lowe case)!
1961   if(choice != '+' && !IS_SHOGI(gameInfo.variant)) {
1962     if (!appData.testLegality || gameInfo.variant == VariantSuicide ||
1963         gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove) ||
1964         gameInfo.variant == VariantGiveaway) {
1965       SetPromo(_("King"), --count, 'k');
1966     }
1967     if(gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove)) {
1968       SetPromo(_("Captain"), --count, 'c');
1969       SetPromo(_("Lieutenant"), --count, 'l');
1970       SetPromo(_("General"), --count, 'g');
1971       SetPromo(_("Warlord"), --count, 'w');
1972     } else {
1973       SetPromo(_("Knight"), --count, 'n');
1974       SetPromo(_("Bishop"), --count, 'b');
1975       SetPromo(_("Rook"), --count, 'r');
1976       if(gameInfo.variant == VariantCapablanca ||
1977          gameInfo.variant == VariantGothic ||
1978          gameInfo.variant == VariantCapaRandom) {
1979         SetPromo(_("Archbishop"), --count, 'a');
1980         SetPromo(_("Chancellor"), --count, 'c');
1981       }
1982       SetPromo(_("Queen"), --count, 'q');
1983       if(gameInfo.variant == VariantChuChess)
1984         SetPromo(_("Lion"), --count, 'l');
1985     }
1986   } else // [HGM] shogi
1987   {
1988       SetPromo(_("Defer"), --count, '=');
1989       SetPromo(_("Promote"), --count, '+');
1990   }
1991   promoOptions[count].min = 0;
1992   GenericPopUp(promoOptions + count, "Promotion", PromoDlg, BoardWindow, NONMODAL, 0);
1993 }
1994
1995 //---------------------------- Chat Windows ----------------------------------------------
1996
1997 static char *line, *memo, *chatMemo, *partner, *texts[MAX_CHAT], dirty[MAX_CHAT], *inputs[MAX_CHAT], *icsLine, *tmpLine;
1998 static int activePartner;
1999 int hidden = 1;
2000
2001 void ChatSwitch P((int n));
2002 int  ChatOK P((int n));
2003
2004 #define CHAT_ICS     6
2005 #define CHAT_PARTNER 8
2006 #define CHAT_OUT    11
2007 #define CHAT_PANE   12
2008 #define CHAT_IN     13
2009
2010 void PaneSwitch P((void));
2011 void ClearChat P((void));
2012
2013 WindowPlacement wpTextMenu;
2014
2015 int
2016 ContextMenu (Option *opt, int button, int x, int y, char *text, int index)
2017 { // callback for ICS-output clicks; handles button 3, passes on other events
2018   int h;
2019   if(button == -3) return TRUE; // supress default GTK context menu on up-click
2020   if(button != 3) return FALSE;
2021   if(index == -1) { // pre-existing selection in memo
2022     strncpy(clickedWord, text, MSG_SIZ);
2023   } else { // figure out what word was clicked
2024     char *start, *end;
2025     start = end = text + index;
2026     while(isalnum(*end)) end++;
2027     while(start > text && isalnum(start[-1])) start--;
2028     clickedWord[0] = NULLCHAR;
2029     if(end-start >= 80) end = start + 80; // intended for small words and numbers
2030     strncpy(clickedWord, start, end-start); clickedWord[end-start] = NULLCHAR;
2031   }
2032   click = !shellUp[TextMenuDlg]; // request auto-popdown of textmenu when we popped it up
2033   h = wpTextMenu.height; // remembered height of text menu
2034   if(h <= 0) h = 65;     // when not available, position w.r.t. top
2035   GetPlacement(ChatDlg, &wpTextMenu);
2036   if(opt->target == (void*) &chatMemo) wpTextMenu.y += (wpTextMenu.height - 30)/2; // click in chat
2037   wpTextMenu.x += x - 50; wpTextMenu.y += y - h + 50; // request positioning
2038   if(wpTextMenu.x < 0) wpTextMenu.x = 0;
2039   if(wpTextMenu.y < 0) wpTextMenu.y = 0;
2040   wpTextMenu.width = wpTextMenu.height = -1;
2041   IcsTextPopUp();
2042   return TRUE;
2043 }
2044
2045 Option chatOptions[] = {
2046 {  0,  0,   0, NULL, NULL, NULL, NULL, Label , N_("Chats:") },
2047 { 1, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
2048 { 2, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
2049 { 3, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
2050 { 4, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
2051 { 5, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
2052 { 250, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &memo, NULL, (void*) &ContextMenu, TextBox, "" },
2053 {  0,  0,   0, NULL, NULL, "", NULL, Break , "" },
2054 { 0,   T_TOP,    100, NULL, (void*) &partner, NULL, NULL, TextBox, N_("Chat partner:") },
2055 {  0, SAME_ROW, 0, NULL, (void*) &ClearChat,  NULL, NULL, Button, N_("End Chat") },
2056 {  0, SAME_ROW, 0, NULL, (void*) &PaneSwitch, NULL, NULL, Button, N_("Hide") },
2057 { 250, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &chatMemo, NULL, (void*) &ContextMenu, TextBox, "" },
2058 {  0,  0,   0, NULL, NULL, "", NULL, Break , "" },
2059 {  0,    0,  510, NULL, (void*) &line, NULL, NULL, TextBox, "" },
2060 { 0, NO_OK|SAME_ROW, 0, NULL, (void*) &ChatOK, NULL, NULL, EndMark , "" }
2061 };
2062
2063 static void
2064 PutText (char *text, int pos)
2065 {
2066     char buf[MSG_SIZ], *p;
2067     DialogClass dlg = ChatDlg;
2068     Option *opt = &chatOptions[CHAT_IN];
2069
2070     if(strstr(text, "$add ") == text) {
2071         GetWidgetText(&boxOptions[INPUT], &p);
2072         snprintf(buf, MSG_SIZ, "%s%s", p, text+5); text = buf;
2073         pos += strlen(p) - 5;
2074     }
2075     if(shellUp[InputBoxDlg]) opt = &boxOptions[INPUT], dlg = InputBoxDlg; // for the benefit of Xaw give priority to ICS Input Box
2076     SetWidgetText(opt, text, dlg);
2077     SetInsertPos(opt, pos);
2078     HardSetFocus(opt, dlg);
2079     CursorAtEnd(opt);
2080 }
2081
2082 int
2083 IcsHist (int n, Option *opt, DialogClass dlg)
2084 {   // [HGM] input: let up-arrow recall previous line from history
2085     char *val = NULL; // to suppress spurious warning
2086     int chat, start;
2087
2088     if(opt != &chatOptions[CHAT_IN] && !(opt == &chatOptions[CHAT_PARTNER] && n == 33)) return 0;
2089     switch(n) {
2090       case 5:
2091         if(!hidden) ClearChat();
2092         break;
2093       case 8:
2094         if(!hidden) PaneSwitch();
2095         break;
2096       case 33: // <Esc>
2097         if(1) BoardToTop(); else
2098         if(hidden) BoardToTop();
2099         else PaneSwitch();
2100         break;
2101       case 15:
2102         NewChat(lastTalker);
2103         break;
2104       case 14:
2105         for(chat=0; chat < MAX_CHAT; chat++) if(!chatPartner[chat][0]) break;
2106         if(chat < MAX_CHAT) ChatSwitch(chat + 1);
2107         break;
2108       case 10: // <Tab>
2109         chat = start = (activePartner - hidden + MAX_CHAT) % MAX_CHAT;
2110         while(!dirty[chat = (chat + 1)%MAX_CHAT]) if(chat == start) break;
2111         if(!dirty[chat])
2112         while(!chatPartner[chat = (chat + 1)%MAX_CHAT][0]) if(chat == start) break;
2113         if(!chatPartner[chat][0]) break; // if all unused, ignore
2114         ChatSwitch(chat + 1);
2115         break;
2116       case 1:
2117         GetWidgetText(opt, &val);
2118         val = PrevInHistory(val);
2119         break;
2120       case -1:
2121         val = NextInHistory();
2122     }
2123     SetWidgetText(opt, val = val ? val : "", dlg);
2124     SetInsertPos(opt, strlen(val));
2125     return 1;
2126 }
2127
2128 void
2129 OutputChatMessage (int partner, char *mess)
2130 {
2131     char *p = texts[partner];
2132     int len = strlen(mess) + 1;
2133
2134     if(!DialogExists(ChatDlg)) return;
2135     if(p) len += strlen(p);
2136     texts[partner] = (char*) malloc(len);
2137     snprintf(texts[partner], len, "%s%s", p ? p : "", mess);
2138     FREE(p);
2139     if(partner == activePartner && !hidden) {
2140         AppendText(&chatOptions[CHAT_OUT], mess);
2141         SetInsertPos(&chatOptions[CHAT_OUT], len-2);
2142     } else {
2143         SetColor("#FFC000", &chatOptions[partner + 1]);
2144         dirty[partner] = 1;
2145     }
2146 }
2147
2148 int
2149 ChatOK (int n)
2150 {   // can only be called through <Enter> in chat-partner text-edit, as there is no OK button
2151     char buf[MSG_SIZ];
2152
2153     if(!hidden && (!partner || strcmp(partner, chatPartner[activePartner]) || !*partner)) {
2154         safeStrCpy(chatPartner[activePartner], partner, MSG_SIZ);
2155         SetWidgetText(&chatOptions[CHAT_OUT], "", -1); // clear text if we alter partner
2156         SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg); // clear text if we alter partner
2157         SetWidgetLabel(&chatOptions[activePartner+1], chatPartner[activePartner][0] ? chatPartner[activePartner] : _("New Chat"));
2158         if(!*partner) PaneSwitch();
2159         HardSetFocus(&chatOptions[CHAT_IN], 0);
2160     }
2161     if(line[0] || hidden) { // something was typed (for ICS commands we also allow empty line!)
2162         SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg);
2163         // from here on it could be back-end
2164         if(line[strlen(line)-1] == '\n') line[strlen(line)-1] = NULLCHAR;
2165         SaveInHistory(line);
2166         if(hidden || !*chatPartner[activePartner]) snprintf(buf, MSG_SIZ, "%s\n", line); else // command for ICS
2167         if(!strcmp("whispers", chatPartner[activePartner]))
2168               snprintf(buf, MSG_SIZ, "whisper %s\n", line); // WHISPER box uses "whisper" to send
2169         else if(!strcmp("shouts", chatPartner[activePartner]))
2170               snprintf(buf, MSG_SIZ, "shout %s\n", line); // SHOUT box uses "shout" to send
2171         else if(!strcmp("c-shouts", chatPartner[activePartner]))
2172               snprintf(buf, MSG_SIZ, "cshout %s\n", line); // C-SHOUT box uses "cshout" to send
2173         else if(!strcmp("kibitzes", chatPartner[activePartner]))
2174               snprintf(buf, MSG_SIZ, "kibitz %s\n", line); // KIBITZ box uses "kibitz" to send
2175         else {
2176             if(!atoi(chatPartner[activePartner])) {
2177                 snprintf(buf, MSG_SIZ, "> %s\n", line); // echo only tells to handle, not channel
2178                 OutputChatMessage(activePartner, buf);
2179                 snprintf(buf, MSG_SIZ, "xtell %s %s\n", chatPartner[activePartner], line);
2180             } else
2181                 snprintf(buf, MSG_SIZ, "tell %s %s\n", chatPartner[activePartner], line);
2182         }
2183         SendToICS(buf);
2184     }
2185     return FALSE; // never pop down
2186 }
2187
2188 void
2189 DelayedSetText ()
2190 {
2191     SetWidgetText(&chatOptions[CHAT_IN], tmpLine, -1); // leave focus on chat-partner field!
2192     SetInsertPos(&chatOptions[CHAT_IN], strlen(tmpLine));
2193 }
2194
2195 void
2196 DelayedScroll ()
2197 {   // If we do this immediately it does it before shrinking the memo, so the lower half remains hidden (Ughh!)
2198     SetInsertPos(&chatOptions[CHAT_ICS], 999999);
2199     SetWidgetText(&chatOptions[CHAT_IN], tmpLine, ChatDlg);
2200     SetInsertPos(&chatOptions[CHAT_IN], strlen(tmpLine));
2201 }
2202
2203 void
2204 ChatSwitch (int n)
2205 {
2206     int i, j;
2207     char *v;
2208     if(chatOptions[CHAT_ICS].type == Skip) hidden = 0; // In Xaw there is no ICS pane we can hide behind
2209     Show(&chatOptions[CHAT_PANE], 0); // show
2210     if(hidden) ScheduleDelayedEvent(DelayedScroll, 50); // Awful!
2211     else ScheduleDelayedEvent(DelayedSetText, 50);
2212     GetWidgetText(&chatOptions[CHAT_IN], &v);
2213     if(hidden) { ASSIGN(icsLine, v); } else { ASSIGN(inputs[activePartner], v); }
2214     hidden = 0;
2215     activePartner = --n;
2216     if(!texts[n]) texts[n] = strdup("");
2217     dirty[n] = 0;
2218     SetWidgetText(&chatOptions[CHAT_OUT], texts[n], ChatDlg);
2219     SetInsertPos(&chatOptions[CHAT_OUT], strlen(texts[n]));
2220     SetWidgetText(&chatOptions[CHAT_PARTNER], chatPartner[n], ChatDlg);
2221     for(i=j=0; i<MAX_CHAT; i++) {
2222         SetWidgetLabel(&chatOptions[++j], *chatPartner[i] ? chatPartner[i] : _("New Chat"));
2223         SetColor(dirty[i] ? "#FFC000" : "#FFFFFF", &chatOptions[j]);
2224     }
2225     if(!inputs[n]) { ASSIGN(inputs[n], ""); }
2226 //    SetWidgetText(&chatOptions[CHAT_IN], inputs[n], ChatDlg); // does not work (in this widget only)
2227 //    SetInsertPos(&chatOptions[CHAT_IN], strlen(inputs[n]));
2228     tmpLine = inputs[n]; // for the delayed event
2229     HardSetFocus(&chatOptions[strcmp(chatPartner[n], "") ? CHAT_IN : CHAT_PARTNER], 0);
2230 }
2231
2232 void
2233 PaneSwitch ()
2234 {
2235     char *v;
2236     Show(&chatOptions[CHAT_PANE], hidden = 1); // hide
2237     GetWidgetText(&chatOptions[CHAT_IN], &v);
2238     ASSIGN(inputs[activePartner], v);
2239     if(!icsLine) { ASSIGN(icsLine, ""); }
2240     tmpLine = icsLine; ScheduleDelayedEvent(DelayedSetText, 50);
2241 //    SetWidgetText(&chatOptions[CHAT_IN], icsLine, ChatDlg); // does not work (in this widget only)
2242 //    SetInsertPos(&chatOptions[CHAT_IN], strlen(icsLine));
2243 }
2244
2245 void
2246 ClearChat ()
2247 {   // clear the chat to make it free for other use
2248     chatPartner[activePartner][0] = NULLCHAR;
2249     ASSIGN(texts[activePartner], "");
2250     ASSIGN(inputs[activePartner], "");
2251     SetWidgetText(&chatOptions[CHAT_PARTNER], "", ChatDlg);
2252     SetWidgetText(&chatOptions[CHAT_OUT], "", ChatDlg);
2253     SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg);
2254     SetWidgetLabel(&chatOptions[activePartner+1], _("New Chat"));
2255     HardSetFocus(&chatOptions[CHAT_PARTNER], 0);
2256 }
2257
2258 static void
2259 NewChat (char *name)
2260 {   // open a chat on program request. If no empty one available, use last
2261     int i;
2262     for(i=0; i<MAX_CHAT-1; i++) if(!chatPartner[i][0]) break;
2263     safeStrCpy(chatPartner[i], name, MSG_SIZ);
2264     ChatSwitch(i+1);
2265 }
2266
2267 void
2268 ConsoleWrite(char *message, int count)
2269 {
2270     if(shellUp[ChatDlg] && chatOptions[CHAT_ICS].type != Skip) { // in Xaw this is a no-op
2271         if(*message == 7) {
2272             message++; // remove bell
2273             if(strcmp(message, "\n")) return;
2274         }
2275         AppendColorized(&chatOptions[CHAT_ICS], message, count);
2276         SetInsertPos(&chatOptions[CHAT_ICS], 999999);
2277     }
2278 }
2279
2280 void
2281 ChatPopUp ()
2282 {
2283     if(GenericPopUp(chatOptions, _("ICS Interaction"), ChatDlg, BoardWindow, NONMODAL, appData.topLevel))
2284         AddHandler(&chatOptions[CHAT_PARTNER], ChatDlg, 2), AddHandler(&chatOptions[CHAT_IN], ChatDlg, 2); // treats return as OK
2285     Show(&chatOptions[CHAT_PANE], hidden = 1); // hide
2286 //    HardSetFocus(&chatOptions[CHAT_IN], 0);
2287     MarkMenu("View.OpenChatWindow", ChatDlg);
2288     CursorAtEnd(&chatOptions[CHAT_IN]);
2289 }
2290
2291 void
2292 ChatProc ()
2293 {
2294     if(shellUp[ChatDlg]) PopDown(ChatDlg);
2295     else ChatPopUp();
2296 }
2297
2298 void
2299 ConsoleAutoPopUp (char *buf)
2300 {
2301         if(*buf == 27) { if(appData.icsActive && DialogExists(ChatDlg)) HardSetFocus (&chatOptions[CHAT_IN], ChatDlg); return; }
2302         if(!appData.autoBox) return;
2303         if(appData.icsActive) { // text typed to board in ICS mode: divert to ICS input box
2304             if(DialogExists(ChatDlg)) { // box already exists: append to current contents
2305                 char *p, newText[MSG_SIZ];
2306                 GetWidgetText(&chatOptions[CHAT_IN], &p);
2307                 snprintf(newText, MSG_SIZ, "%s%c", p, *buf);
2308                 SetWidgetText(&chatOptions[CHAT_IN], newText, ChatDlg);
2309                 if(shellUp[ChatDlg]) HardSetFocus (&chatOptions[CHAT_IN], ChatDlg); //why???
2310             } else { ASSIGN(line, buf); } // box did not exist: make sure it pops up with char in it
2311             ChatPopUp();
2312         } else PopUpMoveDialog(*buf);
2313 }
2314
2315 void
2316 EchoOn ()
2317 {
2318     if(!noEcho) return;
2319     system("stty echo");
2320     WidgetEcho(&chatOptions[CHAT_IN], 1);
2321     noEcho = False;
2322 }
2323
2324 void
2325 EchoOff ()
2326 {
2327     system("stty -echo");
2328     WidgetEcho(&chatOptions[CHAT_IN], 0);
2329     noEcho = True;
2330 }
2331
2332 //--------------------------------- Game-List options dialog ------------------------------------------
2333
2334 char *strings[LPUSERGLT_SIZE];
2335 int stringPtr;
2336
2337 void
2338 GLT_ClearList ()
2339 {
2340     strings[0] = NULL;
2341     stringPtr = 0;
2342 }
2343
2344 void
2345 GLT_AddToList (char *name)
2346 {
2347     strings[stringPtr++] = name;
2348     strings[stringPtr] = NULL;
2349 }
2350
2351 Boolean
2352 GLT_GetFromList (int index, char *name)
2353 {
2354   safeStrCpy(name, strings[index], MSG_SIZ);
2355   return TRUE;
2356 }
2357
2358 void
2359 GLT_DeSelectList ()
2360 {
2361 }
2362
2363 static void GLT_Button P((int n));
2364 static int GLT_OK P((int n));
2365
2366 static Option listOptions[] = {
2367 {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
2368 { 0,    0,     0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("factory") },
2369 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("up") },
2370 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("down") },
2371 { 0, SAME_ROW, 0, NULL, (void*) &GLT_OK, "", NULL, EndMark , "" }
2372 };
2373
2374 static int
2375 GLT_OK (int n)
2376 {
2377     GLT_ParseList();
2378     appData.gameListTags = strdup(lpUserGLT);
2379     GameListUpdate();
2380     return 1;
2381 }
2382
2383 static void
2384 GLT_Button (int n)
2385 {
2386     int index = SelectedListBoxItem (&listOptions[0]);
2387     char *p;
2388     if (index < 0) {
2389         DisplayError(_("No tag selected"), 0);
2390         return;
2391     }
2392     p = strings[index];
2393     if (n == 3) {
2394         if(index >= strlen(GLT_ALL_TAGS)) return;
2395         strings[index] = strings[index+1];
2396         strings[++index] = p;
2397         LoadListBox(&listOptions[0], "?", index, index-1); // only change the two specified entries
2398     } else
2399     if (n == 2) {
2400         if(index == 0) return;
2401         strings[index] = strings[index-1];
2402         strings[--index] = p;
2403         LoadListBox(&listOptions[0], "?", index, index+1);
2404     } else
2405     if (n == 1) {
2406       safeStrCpy(lpUserGLT, GLT_DEFAULT_TAGS, LPUSERGLT_SIZE);
2407       GLT_TagsToList(lpUserGLT);
2408       index = 0;
2409       LoadListBox(&listOptions[0], "?", -1, -1);
2410     }
2411     HighlightListBoxItem(&listOptions[0], index);
2412 }
2413
2414 void
2415 GameListOptionsPopUp (DialogClass parent)
2416 {
2417     safeStrCpy(lpUserGLT, appData.gameListTags, LPUSERGLT_SIZE);
2418     GLT_TagsToList(lpUserGLT);
2419
2420     GenericPopUp(listOptions, _("Game-list options"), TransientDlg, parent, MODAL, 0);
2421 }
2422
2423 void
2424 GameListOptionsProc ()
2425 {
2426     GameListOptionsPopUp(BoardWindow);
2427 }
2428
2429 //----------------------------- Error popup in various uses -----------------------------
2430
2431 /*
2432  * [HGM] Note:
2433  * XBoard has always had some pathologic behavior with multiple simultaneous error popups,
2434  * (which can occur even for modal popups when asynchrounous events, e.g. caused by engine, request a popup),
2435  * and this new implementation reproduces that as well:
2436  * Only the shell of the last instance is remembered in shells[ErrorDlg] (which replaces errorShell),
2437  * so that PopDowns ordered from the code always refer to that instance, and once that is down,
2438  * have no clue as to how to reach the others. For the Delete Window button calling PopDown this
2439  * has now been repaired, as the action routine assigned to it gets the shell passed as argument.
2440  */
2441
2442 int errorUp = False;
2443
2444 void
2445 ErrorPopDown ()
2446 {
2447     if (!errorUp) return;
2448     dialogError = errorUp = False;
2449     PopDown(ErrorDlg); PopDown(FatalDlg); // on explicit request we pop down any error dialog
2450     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
2451 }
2452
2453 int
2454 ErrorOK (int n)
2455 {
2456     dialogError = errorUp = False;
2457     PopDown(n == 1 ? FatalDlg : ErrorDlg); // kludge: non-modal dialogs have one less (dummy) option
2458     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
2459     return FALSE; // prevent second Popdown !
2460 }
2461
2462 static Option errorOptions[] = {
2463 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // dummy option: will never be displayed
2464 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // textValue field will be set before popup
2465 { 0,NO_CANCEL,0, NULL, (void*) &ErrorOK, "", NULL, EndMark , "" }
2466 };
2467
2468 void
2469 ErrorPopUp (char *title, char *label, int modal)
2470 {
2471     errorUp = True;
2472     errorOptions[1].name = label;
2473     if(dialogError = shellUp[TransientDlg])
2474         GenericPopUp(errorOptions+1, title, FatalDlg, TransientDlg, MODAL, 0); // pop up as daughter of the transient dialog
2475     else if(dialogError = shellUp[MasterDlg])
2476         GenericPopUp(errorOptions+1, title, FatalDlg, MasterDlg, MODAL, 0); // pop up as daughter of the master dialog
2477     else
2478         GenericPopUp(errorOptions+modal, title, modal ? FatalDlg: ErrorDlg, BoardWindow, modal, 0); // kludge: option start address indicates modality
2479 }
2480
2481 void
2482 DisplayError (String message, int error)
2483 {
2484     char buf[MSG_SIZ];
2485
2486     if (error == 0) {
2487         if (appData.debugMode || appData.matchMode) {
2488             fprintf(stderr, "%s: %s\n", programName, message);
2489         }
2490     } else {
2491         if (appData.debugMode || appData.matchMode) {
2492             fprintf(stderr, "%s: %s: %s\n",
2493                     programName, message, strerror(error));
2494         }
2495         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
2496         message = buf;
2497     }
2498     ErrorPopUp(_("Error"), message, FALSE);
2499 }
2500
2501
2502 void
2503 DisplayMoveError (String message)
2504 {
2505     fromX = fromY = -1;
2506     ClearHighlights();
2507     DrawPosition(TRUE, NULL); // selective redraw would miss the from-square of the rejected move, displayed empty after drag, but not marked damaged!
2508     if (appData.debugMode || appData.matchMode) {
2509         fprintf(stderr, "%s: %s\n", programName, message);
2510     }
2511     if (appData.popupMoveErrors) {
2512         ErrorPopUp(_("Error"), message, FALSE);
2513     } else {
2514         DisplayMessage(message, "");
2515     }
2516 }
2517
2518
2519 void
2520 DisplayFatalError (String message, int error, int status)
2521 {
2522     char buf[MSG_SIZ];
2523
2524     if(status == 666) { // ignore this error when ICS Console window is up
2525         if(shellUp[ChatDlg]) return;
2526         status = 0;
2527     }
2528
2529     errorExitStatus = status;
2530     if (error == 0) {
2531         fprintf(stderr, "%s: %s\n", programName, message);
2532     } else {
2533         fprintf(stderr, "%s: %s: %s\n",
2534                 programName, message, strerror(error));
2535         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
2536         message = buf;
2537     }
2538     if(mainOptions[W_BOARD].handle) {
2539         if (appData.popupExitMessage) {
2540             if(appData.icsActive) SendToICS("logout\n"); // [HGM] make sure no new games will be started
2541             ErrorPopUp(status ? _("Fatal Error") : _("Exiting"), message, TRUE);
2542         } else {
2543             ExitEvent(status);
2544         }
2545     }
2546 }
2547
2548 void
2549 DisplayInformation (String message)
2550 {
2551     ErrorPopDown();
2552     ErrorPopUp(_("Information"), message, TRUE);
2553 }
2554
2555 void
2556 DisplayNote (String message)
2557 {
2558     ErrorPopDown();
2559     ErrorPopUp(_("Note"), message, FALSE);
2560 }
2561
2562 void
2563 DisplayTitle (char *text)
2564 {
2565     char title[MSG_SIZ];
2566     char icon[MSG_SIZ];
2567
2568     if (text == NULL) text = "";
2569
2570     if(partnerUp) { SetDialogTitle(DummyDlg, text); return; }
2571
2572     if (*text != NULLCHAR) {
2573       safeStrCpy(icon, text, sizeof(icon)/sizeof(icon[0]) );
2574       safeStrCpy(title, text, sizeof(title)/sizeof(title[0]) );
2575     } else if (appData.icsActive) {
2576         snprintf(icon, sizeof(icon), "%s", appData.icsHost);
2577         snprintf(title, sizeof(title), "%s: %s", programName, appData.icsHost);
2578     } else if (appData.cmailGameName[0] != NULLCHAR) {
2579         snprintf(icon, sizeof(icon), "%s", "CMail");
2580         snprintf(title,sizeof(title), "%s: %s", programName, "CMail");
2581 #ifdef GOTHIC
2582     // [HGM] license: This stuff should really be done in back-end, but WinBoard already had a pop-up for it
2583     } else if (gameInfo.variant == VariantGothic) {
2584       safeStrCpy(icon,  programName, sizeof(icon)/sizeof(icon[0]) );
2585       safeStrCpy(title, GOTHIC,     sizeof(title)/sizeof(title[0]) );
2586 #endif
2587 #ifdef FALCON
2588     } else if (gameInfo.variant == VariantFalcon) {
2589       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
2590       safeStrCpy(title, FALCON, sizeof(title)/sizeof(title[0]) );
2591 #endif
2592     } else if (appData.noChessProgram) {
2593       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
2594       safeStrCpy(title, programName, sizeof(title)/sizeof(title[0]) );
2595     } else {
2596       safeStrCpy(icon, first.tidy, sizeof(icon)/sizeof(icon[0]) );
2597         snprintf(title,sizeof(title), "%s: %s", programName, first.tidy);
2598     }
2599     SetWindowTitle(text, title, icon);
2600 }
2601
2602 char *textPtr;
2603 char *texEscapes[] = { "s-1", "s0", "&", "*(L", "*(R", NULL };
2604
2605 int
2606 GetNext(FILE *f)
2607 {
2608     if(textPtr) return *textPtr ? *textPtr++ : EOF;
2609     return fgetc(f);
2610 }
2611
2612 static char *
2613 ReadLine (FILE *f)
2614 {
2615     static char buf[MSG_SIZ];
2616     int i = 0, c;
2617     while((c = GetNext(f)) != '\n') { if(c == EOF) return NULL; buf[i++] = c; }
2618     buf[i] = NULLCHAR;
2619     return buf;
2620 }
2621
2622 void
2623 GetHelpText (FILE *f, char *name)
2624 {
2625     char *line, buf[MSG_SIZ], title[MSG_SIZ], text[10000], *p = text, *q = text;
2626     int len, cnt = 0;
2627     while(*name == '\n') name++;
2628     snprintf(buf, MSG_SIZ, ".B %s", name);
2629     len = strlen(buf);
2630     for(len=3; buf[len] && buf[len] != '(' && buf[len] != ':' && buf[len] != '.' && buf[len] != '?' && buf[len] != '\n'; len++);
2631     buf[len] = NULLCHAR;
2632     while(buf[--len] == ' ') buf[len] = NULLCHAR; len++;
2633     snprintf(title, MSG_SIZ, "Help on '%s'", buf+3);
2634     while((line = ReadLine(f))) {
2635         if(!strncmp(line, buf, len) || !strncmp(line, ".SS ", 4) && !strncmp(line+4, buf+3, len-3)
2636                               || !strncmp(line, ".IX Item \"", 10) && !strncmp(line+10, buf+3, len-3)) {
2637             while((line = ReadLine(f)) && (cnt == 0 || strncmp(line, ".B ", 3) && strncmp(line, ".SS ", 4) && strncmp(line, ".IX ", 4))) {
2638                 if(!*line) { *p++ = '\n'; *p++ = '\n'; q = p; continue; }
2639                 if(*line == '.') continue;
2640                 *p++ = ' '; cnt++;
2641                 while(*line) {
2642                     if(*line < ' ') { line++; continue;}
2643                     if(*line == '\\') {
2644                         char **esc;
2645                         line++;
2646                         for(esc = texEscapes; *esc; esc++) {
2647                             len = strlen(*esc);
2648                             if(!strncmp(*esc, line, len)) {
2649                                 line += len;
2650                                 break;
2651                             }
2652                         }
2653                         continue;
2654                     }
2655                     if(*line == ' ' && p - q > 80) *line = '\n', q = p;
2656                     *p++ = *line++;
2657                 }
2658                 if(p - text > 9900) break;
2659             }
2660             *p = NULLCHAR;
2661             ErrorPopUp(title, text, FALSE);
2662             return;
2663         }
2664     }
2665     snprintf(text, MSG_SIZ, "No help available on '%s'\n", buf+3);
2666     DisplayNote(text);
2667 }
2668
2669 void
2670 DisplayHelp (char *name)
2671 {
2672     static char *xboardMan, *manText[2], tidy[MSG_SIZ], engMan[MSG_SIZ];
2673     char buf[MSG_SIZ], adapter[MSG_SIZ], *eng;
2674     int n = 0;
2675     FILE *f;
2676     if(!xboardMan) {
2677         xboardMan = BufferCommandOutput("man -w xboard", MSG_SIZ); // obtain path to XBoard's man file
2678         if(xboardMan) xboardMan[strlen(xboardMan)-1] = NULLCHAR;   // strip off traling linefeed
2679     }
2680     if(currentCps) { // for engine options we have to look in engine manual
2681         snprintf(buf, MSG_SIZ, "man -w ");            // get (tidied) engine name in buf
2682         TidyProgramName(currentCps->program, "localhost", adapter);       // name of binary we are actually running
2683         TidyProgramName(currentCps == &first ? appData.firstChessProgram : appData.secondChessProgram, "localhost", buf+7);
2684         if(strcmp(buf+7, adapter) && StrCaseStr(name, adapter) == name) { // option starts with name of apparent proxy for engine
2685             safeStrCpy(buf+7, adapter, MSG_SIZ-7);    // use adapter manual
2686             name += strlen(adapter);                  // strip adapter name of option
2687             while(*name == ' ') name++;
2688         }
2689         if(strcmp(buf, tidy)) {                       // is different engine from last time
2690             FREE(manText[1]); manText[1] = NULL;      // so any currently held text is worthless
2691             safeStrCpy(tidy, buf, MSG_SIZ);           // remember current engine
2692             eng = BufferCommandOutput(tidy, MSG_SIZ); // obtain path to  its man file
2693             safeStrCpy(engMan, eng, strlen(eng));     // and remember that too
2694             FREE(eng);
2695         }
2696         safeStrCpy(buf, engMan, MSG_SIZ); n = 1;      // use engine man
2697     } else snprintf(buf, MSG_SIZ, "%s", xboardMan);   // use xboard man
2698     f = fopen(buf, "r");
2699     if(f) {
2700         char *msg = "Right-clicking menu item or dialog text pops up help on it";
2701         ASSIGN(appData.suppress, msg);
2702         if(strstr(buf, ".gz")) { // man file is gzipped
2703             if(!manText[n]) {    // unzipped text not buffered yet
2704                 snprintf(tidy, MSG_SIZ, "gunzip -c %s", buf);
2705                 manText[n] = BufferCommandOutput(tidy, 250000); // store unzipped in buffer
2706             }
2707             textPtr = manText[n];// use buffered unzipped text
2708         } else textPtr = NULL;   // use plaintext man file directly
2709         GetHelpText(f, name);
2710         fclose(f);
2711     } else if(currentCps) DisplayNote("No manual is installed for this engine");
2712 }
2713
2714 #define PAUSE_BUTTON "P"
2715 #define PIECE_MENU_SIZE 18
2716 static String pieceMenuStrings[2][PIECE_MENU_SIZE+1] = {
2717     { N_("White"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
2718       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
2719       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
2720       N_("Empty square"), N_("Clear board"), NULL },
2721     { N_("Black"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
2722       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
2723       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
2724       N_("Empty square"), N_("Clear board"), NULL }
2725 };
2726 /* must be in same order as pieceMenuStrings! */
2727 static ChessSquare pieceMenuTranslation[2][PIECE_MENU_SIZE] = {
2728     { WhitePlay, (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2729         WhiteRook, WhiteQueen, WhiteKing, (ChessSquare) 0, WhiteAlfil,
2730         WhiteCannon, WhiteAngel, WhiteMarshall, (ChessSquare) 0,
2731         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2732     { BlackPlay, (ChessSquare) 0, BlackPawn, BlackKnight, BlackBishop,
2733         BlackRook, BlackQueen, BlackKing, (ChessSquare) 0, BlackAlfil,
2734         BlackCannon, BlackAngel, BlackMarshall, (ChessSquare) 0,
2735         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2736 };
2737
2738 #define DROP_MENU_SIZE 6
2739 static String dropMenuStrings[DROP_MENU_SIZE+1] = {
2740     "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"), N_("Queen"), NULL
2741   };
2742 /* must be in same order as dropMenuStrings! */
2743 static ChessSquare dropMenuTranslation[DROP_MENU_SIZE] = {
2744     (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2745     WhiteRook, WhiteQueen
2746 };
2747
2748 // [HGM] experimental code to pop up window just like the main window, using GenercicPopUp
2749
2750 static Option *Exp P((int n, int x, int y));
2751 void MenuCallback P((int n));
2752 void SizeKludge P((int n));
2753 static Option *LogoW P((int n, int x, int y));
2754 static Option *LogoB P((int n, int x, int y));
2755
2756 static int pmFromX = -1, pmFromY = -1;
2757 void *userLogo;
2758
2759 void
2760 DisplayLogos (Option *w1, Option *w2)
2761 {
2762         void *whiteLogo = first.programLogo, *blackLogo = second.programLogo;
2763         if(appData.autoLogo) {
2764           if(appData.noChessProgram) whiteLogo = blackLogo = NULL;
2765           if(appData.icsActive) whiteLogo = blackLogo = second.programLogo;
2766           switch(gameMode) { // pick logos based on game mode
2767             case IcsObserving:
2768                 whiteLogo = second.programLogo; // ICS logo
2769                 blackLogo = second.programLogo;
2770             default:
2771                 break;
2772             case IcsPlayingWhite:
2773                 if(!appData.zippyPlay) whiteLogo = userLogo;
2774                 blackLogo = second.programLogo; // ICS logo
2775                 break;
2776             case IcsPlayingBlack:
2777                 whiteLogo = second.programLogo; // ICS logo
2778                 blackLogo = appData.zippyPlay ? first.programLogo : userLogo;
2779                 break;
2780             case TwoMachinesPlay:
2781                 if(first.twoMachinesColor[0] == 'b') {
2782                     whiteLogo = second.programLogo;
2783                     blackLogo = first.programLogo;
2784                 }
2785                 break;
2786             case MachinePlaysWhite:
2787                 blackLogo = userLogo;
2788                 break;
2789             case MachinePlaysBlack:
2790                 whiteLogo = userLogo;
2791                 blackLogo = first.programLogo;
2792           }
2793         }
2794         DrawLogo(w1, whiteLogo);
2795         DrawLogo(w2, blackLogo);
2796 }
2797
2798 static void
2799 PMSelect (int n)
2800 {   // user callback for board context menus
2801     if (pmFromX < 0 || pmFromY < 0) return;
2802     if(n == W_DROP) DropMenuEvent(dropMenuTranslation[values[n]], pmFromX, pmFromY);
2803     else EditPositionMenuEvent(pieceMenuTranslation[n - W_MENUW][values[n]], pmFromX, pmFromY);
2804 }
2805
2806 static void
2807 CCB (int n)
2808 {
2809     shiftKey = (ShiftKeys() & 3) != 0;
2810     if(n < 0) { // button != 1
2811         n = -n;
2812         if(shiftKey && (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack)) {
2813             AdjustClock(n == W_BLACK, 1);
2814         }
2815     } else
2816     ClockClick(n == W_BLACK);
2817 }
2818
2819 Option mainOptions[] = { // description of main window in terms of generic dialog creator
2820 { 0, 0xCA, 0, NULL, NULL, "", NULL, BarBegin, "" }, // menu bar
2821   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_File") },
2822   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Edit") },
2823   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_View") },
2824   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Mode") },
2825   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Action") },
2826   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("E_ngine") },
2827   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Options") },
2828   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Help") },
2829 { 0, 0, 0, NULL, (void*)&SizeKludge, "", NULL, BarEnd, "" },
2830 { 0, LR|T2T|BORDER|SAME_ROW, 0, NULL, NULL, NULL, NULL, Label, "1" }, // optional title in window
2831 { 50,    LL|TT,            100, NULL, (void*) &LogoW, NULL, NULL, Skip, "" }, // white logo
2832 { 12,   L2L|T2T,           200, NULL, (void*) &CCB, NULL, NULL, Label, "White" }, // white clock
2833 { 13,   R2R|T2T|SAME_ROW,  200, NULL, (void*) &CCB, NULL, NULL, Label, "Black" }, // black clock
2834 { 50,    RR|TT|SAME_ROW,   100, NULL, (void*) &LogoB, NULL, NULL, Skip, "" }, // black logo
2835 { 0, LR|T2T|BORDER,        401, NULL, NULL, "", NULL, Skip, "2" }, // backup for title in window (if no room for other)
2836 { 0, LR|T2T|BORDER,        270, NULL, NULL, NULL, NULL, Label, "message", &appData.font }, // message field
2837 { 0, RR|TT|SAME_ROW,       125, NULL, NULL, "", NULL, BoxBegin, "" }, // (optional) button bar
2838   { 0,    0,     0, NULL, (void*) &ToStartEvent,  NULL, NULL, Button, N_("<<"), &appData.font },
2839   { 0, SAME_ROW, 0, NULL, (void*) &BackwardEvent, NULL, NULL, Button, N_("<"),  &appData.font },
2840   { 0, SAME_ROW, 0, NULL, (void*) &PauseEvent,    NULL, NULL, Button, N_(PAUSE_BUTTON), &appData.font },
2841   { 0, SAME_ROW, 0, NULL, (void*) &ForwardEvent,  NULL, NULL, Button, N_(">"),  &appData.font },
2842   { 0, SAME_ROW, 0, NULL, (void*) &ToEndEvent,    NULL, NULL, Button, N_(">>"), &appData.font },
2843 { 0, 0, 0, NULL, NULL, "", NULL, BoxEnd, "" },
2844 { 401, LR|TB, 401, NULL, (char*) &Exp, NULL, NULL, Graph, "shadow board" }, // board
2845   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[0], PopUp, "menuW" },
2846   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[1], PopUp, "menuB" },
2847   { -1, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, dropMenuStrings, PopUp, "menuD" },
2848 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2849 };
2850
2851 Option *
2852 LogoW (int n, int x, int y)
2853 {
2854     if(n == 10) DisplayLogos(&mainOptions[W_WHITE-1], NULL);
2855     return NULL;
2856 }
2857
2858 Option *
2859 LogoB (int n, int x, int y)
2860 {
2861     if(n == 10) DisplayLogos(NULL, &mainOptions[W_BLACK+1]);
2862     return NULL;
2863 }
2864
2865 void
2866 SizeKludge (int n)
2867 {   // callback called by GenericPopUp immediately after sizing the menu bar
2868     int width = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2869     int w = width - 44 - mainOptions[n].min;
2870     mainOptions[W_TITLE].max = w; // width left behind menu bar
2871     if(w < 0.4*width) // if no reasonable amount of space for title, force small layout
2872         mainOptions[W_SMALL].type = mainOptions[W_TITLE].type, mainOptions[W_TITLE].type = Skip;
2873 }
2874
2875 void
2876 MenuCallback (int n)
2877 {
2878     MenuProc *proc = (MenuProc *) (((MenuItem*)(mainOptions[n].choice))[values[n]].proc);
2879
2880     if(!proc) RecentEngineEvent(values[n] - firstEngineItem); else (proc)();
2881 }
2882
2883 static Option *
2884 Exp (int n, int x, int y)
2885 {
2886     static int but1, but3, oldW, oldH, oldX, oldY;
2887     int menuNr = -3, sizing, f, r;
2888     TimeMark now;
2889     extern Boolean right;
2890
2891     if(right) {  // kludgy way to let button 1 double as button 3 when back-end requests this
2892         if(but1 && n == 0) but1 = 0, but3 = 1;
2893         else if(n == -1) n = -3, right = FALSE;
2894     }
2895
2896     if(n == 0) { // motion
2897         oldX = x; oldY = y;
2898         if(SeekGraphClick(Press, x, y, 1)) return NULL;
2899         if((but1 || dragging == 2) && !PromoScroll(x, y)) DragPieceMove(x, y);
2900         if(but3) MovePV(x, y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
2901         if(appData.highlightDragging) {
2902             f = EventToSquare(x, BOARD_WIDTH);  if ( flipView && f >= 0) f = BOARD_WIDTH - 1 - f;
2903             r = EventToSquare(y, BOARD_HEIGHT); if (!flipView && r >= 0) r = BOARD_HEIGHT - 1 - r;
2904             HoverEvent(x, y, f, r);
2905         }
2906         return NULL;
2907     }
2908     if(n != 10 && PopDown(PromoDlg)) fromX = fromY = -1; // user starts fiddling with board when promotion dialog is up
2909     else GetTimeMark(&now);
2910     shiftKey = ShiftKeys();
2911     controlKey = (shiftKey & 0xC) != 0;
2912     shiftKey = (shiftKey & 3) != 0;
2913     switch(n) {
2914         case  1: LeftClick(Press,   x, y), but1 = 1; break;
2915         case -1: LeftClick(Release, x, y), but1 = 0; break;
2916         case  2: shiftKey = !shiftKey;
2917         case  3: menuNr = RightClick(Press,   x, y, &pmFromX, &pmFromY), but3 = 1; break;
2918         case -2: shiftKey = !shiftKey;
2919         case -3: menuNr = RightClick(Release, x, y, &pmFromX, &pmFromY), but3 = 0; break;
2920         case  4: Wheel(-1, oldX, oldY); break;
2921         case  5: Wheel(1, oldX, oldY); break;
2922         case 10:
2923             sizing = (oldW != x || oldH != y);
2924             oldW = x; oldH = y;
2925             InitDrawingHandle(mainOptions + W_BOARD);
2926             if(sizing && SubtractTimeMarks(&now, &programStartTime) > 10000) return NULL; // don't redraw while sizing (except at startup)
2927             DrawPosition(True, NULL);
2928         default:
2929             return NULL;
2930     }
2931
2932     switch(menuNr) {
2933       case 0: return &mainOptions[shiftKey ? W_MENUW: W_MENUB];
2934       case 1: SetupDropMenu(); return &mainOptions[W_DROP];
2935       case 2:
2936       case -1: ErrorPopDown();
2937       case -2:
2938       default: break; // -3, so no clicks caught
2939     }
2940     return NULL;
2941 }
2942
2943 Option *
2944 BoardPopUp (int squareSize, int lineGap, void *clockFontThingy)
2945 {
2946     int i, size = BOARD_WIDTH*(squareSize + lineGap) + lineGap, logo = appData.logoSize;
2947     int f = 2*appData.fixedSize; // width fudge, needed for unknown reasons to not clip board
2948     mainOptions[W_WHITE].choice = (char**) clockFontThingy;
2949     mainOptions[W_BLACK].choice = (char**) clockFontThingy;
2950     mainOptions[W_BOARD].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2951     mainOptions[W_BOARD].max = mainOptions[W_SMALL].max = size; // board size
2952     mainOptions[W_SMALL].max = size - 2; // board title (subtract border!)
2953     mainOptions[W_BLACK].max = mainOptions[W_WHITE].max = size/2-3; // clock width
2954     mainOptions[W_MESSG].max = appData.showButtonBar ? size-135+f : size-2+f; // message
2955     mainOptions[W_MENU].max = size-40; // menu bar
2956     mainOptions[W_TITLE].type = appData.titleInWindow ? Label : Skip ;
2957     if(logo && logo <= size/4) { // Activate logos
2958         mainOptions[W_WHITE-1].type = mainOptions[W_BLACK+1].type = Graph;
2959         mainOptions[W_WHITE-1].max  = mainOptions[W_BLACK+1].max  = logo;
2960         mainOptions[W_WHITE-1].value= mainOptions[W_BLACK+1].value= logo/2;
2961         mainOptions[W_WHITE].min  |= SAME_ROW;
2962         mainOptions[W_WHITE].max  = mainOptions[W_BLACK].max  -= logo + 4;
2963         mainOptions[W_WHITE].name = mainOptions[W_BLACK].name = "Double\nHeight";
2964     }
2965     if(!appData.showButtonBar) for(i=W_BUTTON; i<W_BOARD; i++) mainOptions[i].type = Skip;
2966     for(i=0; i<8; i++) mainOptions[i+1].choice = (char**) menuBar[i].mi;
2967     AppendEnginesToMenu(appData.recentEngineList);
2968     GenericPopUp(mainOptions, "XBoard", BoardWindow, BoardWindow, NONMODAL, 1); // allways top-level
2969     return mainOptions;
2970 }
2971
2972 static Option *
2973 SlaveExp (int n, int x, int y)
2974 {
2975     if(n == 10) { // expose event
2976         flipView = !flipView; partnerUp = !partnerUp;
2977         DrawPosition(True, NULL); // [HGM] dual: draw other board in other orientation
2978         flipView = !flipView; partnerUp = !partnerUp;
2979     }
2980     return NULL;
2981 }
2982
2983 Option dualOptions[] = { // auxiliary board window
2984 { 0, L2L|T2T,              198, NULL, NULL, NULL, NULL, Label, "White" }, // white clock
2985 { 0, R2R|T2T|SAME_ROW,     198, NULL, NULL, NULL, NULL, Label, "Black" }, // black clock
2986 { 0, LR|T2T|BORDER,        401, NULL, NULL, NULL, NULL, Label, "This feature is experimental" }, // message field
2987 { 401, LR|TT, 401, NULL, (char*) &SlaveExp, NULL, NULL, Graph, "shadow board" }, // board
2988 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2989 };
2990
2991 void
2992 SlavePopUp ()
2993 {
2994     int size = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2995     // copy params from main board
2996     dualOptions[0].choice = mainOptions[W_WHITE].choice;
2997     dualOptions[1].choice = mainOptions[W_BLACK].choice;
2998     dualOptions[3].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2999     dualOptions[3].max = dualOptions[2].max = size; // board width
3000     dualOptions[0].max = dualOptions[1].max = size/2 - 3; // clock width
3001     GenericPopUp(dualOptions, "XBoard", DummyDlg, BoardWindow, NONMODAL, appData.topLevel);
3002     SlaveResize(dualOptions+3);
3003 }
3004
3005 void
3006 DisplayWhiteClock (long timeRemaining, int highlight)
3007 {
3008     if(appData.noGUI) return;
3009     if(twoBoards && partnerUp) {
3010         DisplayTimerLabel(&dualOptions[0], _("White"), timeRemaining, highlight);
3011         return;
3012     }
3013     DisplayTimerLabel(&mainOptions[W_WHITE], _("White"), timeRemaining, highlight);
3014     if(highlight) SetClockIcon(0);
3015 }
3016
3017 void
3018 DisplayBlackClock (long timeRemaining, int highlight)
3019 {
3020     if(appData.noGUI) return;
3021     if(twoBoards && partnerUp) {
3022         DisplayTimerLabel(&dualOptions[1], _("Black"), timeRemaining, highlight);
3023         return;
3024     }
3025     DisplayTimerLabel(&mainOptions[W_BLACK], _("Black"), timeRemaining, highlight);
3026     if(highlight) SetClockIcon(1);
3027 }
3028
3029
3030 //---------------------------------------------
3031
3032 void
3033 DisplayMessage (char *message, char *extMessage)
3034 {
3035   /* display a message in the message widget */
3036
3037   char buf[MSG_SIZ];
3038
3039   if (extMessage)
3040     {
3041       if (*message)
3042         {
3043           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
3044           message = buf;
3045         }
3046       else
3047         {
3048           message = extMessage;
3049         };
3050     };
3051
3052     safeStrCpy(lastMsg, message, MSG_SIZ); // [HGM] make available
3053
3054   /* need to test if messageWidget already exists, since this function
3055      can also be called during the startup, if for example a Xresource
3056      is not set up correctly */
3057   if(mainOptions[W_MESSG].handle)
3058     SetWidgetLabel(&mainOptions[W_MESSG], message);
3059
3060   return;
3061 }
3062
3063 //----------------------------------- File Browser -------------------------------
3064
3065 #ifdef HAVE_DIRENT_H
3066 #include <dirent.h>
3067 #else
3068 #include <sys/dir.h>
3069 #define dirent direct
3070 #endif
3071
3072 #include <sys/stat.h>
3073
3074 #define MAXFILES 1000
3075
3076 static DialogClass savDlg;
3077 static ChessProgramState *savCps;
3078 static FILE **savFP;
3079 static char *fileName, *extFilter, *savMode, **namePtr;
3080 static int folderPtr, filePtr, oldVal, byExtension, extFlag, pageStart, cnt;
3081 static char curDir[MSG_SIZ], title[MSG_SIZ], *folderList[MAXFILES], *fileList[MAXFILES];
3082
3083 static char *FileTypes[] = {
3084 "Chess Games",
3085 "Chess Positions",
3086 "Tournaments",
3087 "Opening Books",
3088 "Sound files",
3089 "Images",
3090 "Settings (*.ini)",
3091 "Log files",
3092 "All files",
3093 NULL,
3094 "PGN",
3095 "Old-Style Games",
3096 "FEN",
3097 "Old-Style Positions",
3098 NULL,
3099 NULL
3100 };
3101
3102 static char *Extensions[] = {
3103 ".pgn .game",
3104 ".fen .epd .pos",
3105 ".trn",
3106 ".bin",
3107 ".wav",
3108 ".png",
3109 ".ini",
3110 ".log",
3111 "",
3112 "INVALID",
3113 ".pgn",
3114 ".game",
3115 ".fen",
3116 ".pos",
3117 NULL,
3118 ""
3119 };
3120
3121 void DirSelProc P((int n, int sel));
3122 void FileSelProc P((int n, int sel));
3123 void SetTypeFilter P((int n));
3124 int BrowseOK P((int n));
3125 void Switch P((int n));
3126 void CreateDir P((int n));
3127
3128 Option browseOptions[] = {
3129 {   0,    LR|T2T,      500, NULL, NULL, NULL, NULL, Label, title },
3130 {   0,    L2L|T2T,     250, NULL, NULL, NULL, NULL, Label, N_("Directories:") },
3131 {   0,R2R|T2T|SAME_ROW,100, NULL, NULL, NULL, NULL, Label, N_("Files:") },
3132 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by name") },
3133 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by type") },
3134 { 300,    L2L|TB,      250, NULL, (void*) folderList, (char*) &DirSelProc, NULL, ListBox, "" },
3135 { 300, R2R|TB|SAME_ROW,250, NULL, (void*) fileList, (char*) &FileSelProc, NULL, ListBox, "" },
3136 {   0,       0,        300, NULL, (void*) &fileName, NULL, NULL, TextBox, N_("Filename:") },
3137 {   0,    SAME_ROW,    120, NULL, (void*) &CreateDir, NULL, NULL, Button, N_("New directory") },
3138 {   0, COMBO_CALLBACK, 150, NULL, (void*) &SetTypeFilter, NULL, FileTypes, ComboBox, N_("File type:") },
3139 {   0,    SAME_ROW,      0, NULL, (void*) &BrowseOK, "", NULL, EndMark , "" }
3140 };
3141
3142 int
3143 BrowseOK (int n)
3144 {
3145         if(!fileName[0]) { // it is enough to have a file selected
3146             if(browseOptions[6].textValue) { // kludge: if callback specified we browse for file
3147                 int sel = SelectedListBoxItem(&browseOptions[6]);
3148                 if(sel < 0 || sel >= filePtr) return FALSE;
3149                 ASSIGN(fileName, fileList[sel]);
3150             } else { // we browse for path
3151                 ASSIGN(fileName, curDir); // kludge: without callback we browse for path
3152             }
3153         }
3154         if(!fileName[0]) return FALSE; // refuse OK when no file
3155         if(!savMode[0]) { // browsing for name only (dialog Browse button)
3156                 if(fileName[0] == '/') // We already had a path name
3157                     snprintf(title, MSG_SIZ, "%s", fileName);
3158                 else
3159                     snprintf(title, MSG_SIZ, "%s/%s", curDir, fileName);
3160                 SetWidgetText((Option*) savFP, title, savDlg);
3161                 currentCps = savCps; // could return to Engine Settings dialog!
3162                 return TRUE;
3163         }
3164         *savFP = fopen(fileName, savMode);
3165         if(*savFP == NULL) return FALSE; // refuse OK if file not openable
3166         ASSIGN(*namePtr, fileName);
3167         ScheduleDelayedEvent(DelayedLoad, 50);
3168         currentCps = savCps; // not sure this is ever non-null
3169         return TRUE;
3170 }
3171
3172 int
3173 AlphaNumCompare (char *p, char *q)
3174 {
3175     while(*p) {
3176         if(isdigit(*p) && isdigit(*q) && atoi(p) != atoi(q))
3177              return (atoi(p) > atoi(q) ? 1 : -1);
3178         if(*p != *q) break;
3179         p++, q++;
3180     }
3181     if(*p == *q) return 0;
3182     return (*p > *q ? 1 : -1);
3183 }
3184
3185 int
3186 Comp (const void *s, const void *t)
3187 {
3188     char *p = *(char**) s, *q = *(char**) t;
3189     if(extFlag) {
3190         char *h; int r;
3191         while(h = strchr(p, '.')) p = h+1;
3192         if(p == *(char**) s) p = "";
3193         while(h = strchr(q, '.')) q = h+1;
3194         if(q == *(char**) t) q = "";
3195         r = AlphaNumCompare(p, q);
3196         if(r) return r;
3197     }
3198     return AlphaNumCompare( *(char**) s, *(char**) t );
3199 }
3200
3201 void
3202 ListDir (int pathFlag)
3203 {
3204         DIR *dir;
3205         struct dirent *dp;
3206         struct stat statBuf;
3207         static int lastFlag;
3208
3209         if(pathFlag < 0) pathFlag = lastFlag;
3210         lastFlag = pathFlag;
3211         dir = opendir(".");
3212         getcwd(curDir, MSG_SIZ);
3213         snprintf(title, MSG_SIZ, "%s   %s", _("Contents of"), curDir);
3214         folderPtr = filePtr = cnt = 0; // clear listing
3215
3216         while (dp = readdir(dir)) { // pass 1: list foders
3217             char *s = dp->d_name;
3218             if(!stat(s, &statBuf) && S_ISDIR(statBuf.st_mode)) { // stat succeeds and tells us it is directory
3219                 if(s[0] == '.' && strcmp(s, "..")) continue; // suppress hidden, except ".."
3220                 ASSIGN(folderList[folderPtr], s); if(folderPtr < MAXFILES-2) folderPtr++;
3221             } else if(!pathFlag) {
3222                 char *s = dp->d_name, match=0;
3223 //              if(cnt == pageStart) { ASSIGN }
3224                 if(s[0] == '.') continue; // suppress hidden files
3225                 if(extFilter[0]) { // [HGM] filter on extension
3226                     char *p = extFilter, *q;
3227                     do {
3228                         if(q = strchr(p, ' ')) *q = 0;
3229                         if(strstr(s, p)) match++;
3230                         if(q) *q = ' ';
3231                     } while(q && (p = q+1));
3232                     if(!match) continue;
3233                 }
3234                 if(filePtr == MAXFILES-2) continue;
3235                 if(cnt++ < pageStart) continue;
3236                 ASSIGN(fileList[filePtr], s); filePtr++;
3237             }
3238         }
3239         if(filePtr == MAXFILES-2) { ASSIGN(fileList[filePtr], _("  next page")); filePtr++; }
3240         FREE(folderList[folderPtr]); folderList[folderPtr] = NULL;
3241         FREE(fileList[filePtr]); fileList[filePtr] = NULL;
3242         closedir(dir);
3243         extFlag = 0;         qsort((void*)folderList, folderPtr, sizeof(char*), &Comp);
3244         extFlag = byExtension; qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
3245 }
3246
3247 void
3248 Refresh (int pathFlag)
3249 {
3250     ListDir(pathFlag); // and make new one
3251     LoadListBox(&browseOptions[5], "", -1, -1);
3252     LoadListBox(&browseOptions[6], "", -1, -1);
3253     SetWidgetLabel(&browseOptions[0], title);
3254 }
3255
3256 static char msg1[] = N_("FIRST TYPE DIRECTORY NAME HERE");
3257 static char msg2[] = N_("TRY ANOTHER NAME");
3258
3259 void
3260 CreateDir (int n)
3261 {
3262     char *name, *errmsg = "";
3263     GetWidgetText(&browseOptions[n-1], &name);
3264     if(!strcmp(name, msg1) || !strcmp(name, msg2)) return;
3265     if(!name[0]) errmsg = _(msg1); else
3266     if(mkdir(name, 0755)) errmsg = _(msg2);
3267     else {
3268         chdir(name);
3269         Refresh(-1);
3270     }
3271     SetWidgetText(&browseOptions[n-1], errmsg, BrowserDlg);
3272 }
3273
3274 void
3275 Switch (int n)
3276 {
3277     if(byExtension == (n == 4)) return;
3278     extFlag = byExtension = (n == 4);
3279     qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
3280     LoadListBox(&browseOptions[6], "", -1, -1);
3281 }
3282
3283 void
3284 SetTypeFilter (int n)
3285 {
3286     int j = values[n];
3287     if(j == browseOptions[n].value) return; // no change
3288     browseOptions[n].value = j;
3289     SetWidgetLabel(&browseOptions[n], FileTypes[j]);
3290     ASSIGN(extFilter, Extensions[j]);
3291     pageStart = 0;
3292     Refresh(-1); // uses pathflag remembered by ListDir
3293     values[n] = oldVal; // do not disturb combo settings of underlying dialog
3294 }
3295
3296 void
3297 FileSelProc (int n, int sel)
3298 {
3299     if(sel < 0 || fileList[sel] == NULL) return;
3300     if(sel == MAXFILES-2) { pageStart = cnt; Refresh(-1); return; }
3301     ASSIGN(fileName, fileList[sel]);
3302     if(BrowseOK(0)) PopDown(BrowserDlg);
3303 }
3304
3305 void
3306 DirSelProc (int n, int sel)
3307 {
3308     if(!chdir(folderList[sel])) { // cd succeeded, so we are in new directory now
3309         Refresh(-1);
3310     }
3311 }
3312
3313 void
3314 StartDir (char *filter, char *newName)
3315 {
3316     static char *gamesDir, *trnDir, *imgDir, *bookDir, *dirDir;
3317     static char curDir[MSG_SIZ];
3318     char **res = NULL;
3319     if(!filter || !*filter) return;
3320     if(strstr(filter, "dir")) {
3321         res = &dirDir;
3322         if(!dirDir) dirDir= strdup(dataDir);
3323     } else
3324     if(strstr(filter, "pgn")) res = &gamesDir; else
3325     if(strstr(filter, "bin")) res = &bookDir; else
3326     if(strstr(filter, "png")) res = &imgDir; else
3327     if(strstr(filter, "trn")) res = &trnDir; else
3328     if(strstr(filter, "fen")) res = &appData.positionDir;
3329     if(res) {
3330         if(newName) {
3331             char *p, *q;
3332             if(*newName) {
3333                 ASSIGN(*res, newName);
3334                 for(p=*res; q=strchr(p, '/');) p = q + 1; *p = NULLCHAR;
3335             }
3336         }
3337         if(*curDir) {
3338             chdir(curDir);
3339             *curDir = NULLCHAR;
3340         } else {
3341             getcwd(curDir, MSG_SIZ);
3342             if(*res && **res) chdir(*res);
3343         }
3344     }
3345 }
3346
3347 void
3348 Browse (DialogClass dlg, char *label, char *proposed, char *ext, Boolean pathFlag, char *mode, char **name, FILE **fp)
3349 {
3350     int j=0;
3351     savFP = fp; savMode = mode, namePtr = name, savCps = currentCps, oldVal = values[9], savDlg = dlg; // save params, for use in callback
3352     ASSIGN(extFilter, ext);
3353     ASSIGN(fileName, proposed ? proposed : "");
3354     for(j=0; Extensions[j]; j++) // look up actual value in list of possible values, to get selection nr
3355         if(extFilter && !strcmp(extFilter, Extensions[j])) break;
3356     if(Extensions[j] == NULL) { j++; ASSIGN(FileTypes[j], extFilter); }
3357     browseOptions[9].value = j;
3358     browseOptions[6].textValue = (char*) (pathFlag ? NULL : &FileSelProc); // disable file listbox during path browsing
3359     pageStart = 0; ListDir(pathFlag);
3360     currentCps = NULL;
3361     GenericPopUp(browseOptions, label, BrowserDlg, dlg, MODAL, 0);
3362     SetWidgetLabel(&browseOptions[9], FileTypes[j]);
3363 }
3364
3365 static char *openName;
3366 FileProc fileProc;
3367 char *fileOpenMode;
3368 FILE *openFP;
3369
3370 void
3371 DelayedLoad ()
3372 {
3373   (void) (*fileProc)(openFP, 0, openName);
3374 }
3375
3376 void
3377 FileNamePopUp (char *label, char *def, char *filter, FileProc proc, char *openMode)
3378 {
3379     fileProc = proc;            /* I can't see a way not */
3380     fileOpenMode = openMode;    /*   to use globals here */
3381     FileNamePopUpWrapper(label, def, filter, proc, False, openMode, &openName, &openFP);
3382 }
3383
3384 void
3385 ActivateTheme (int col)
3386 {
3387     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap; else lineGap = defaultLineGap;
3388     InitDrawingParams(strcmp(oldPieceDir, appData.pieceDirectory));
3389     InitDrawingSizes(-1, 0);
3390     DrawPosition(True, NULL);
3391 }
3392