Fix multi-leg promotions
[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
1045     if(q = strstr(p, "$name")) { // in Xaw this is already intercepted
1046         if(!shellUp[TextMenuDlg] || !clickedWord[0]) return;
1047         strncpy(buf2, p, MSG_SIZ);
1048         snprintf(buf2 + (q-p), MSG_SIZ -(q-p), "%s%s", clickedWord, q+5);
1049         p = buf2;
1050     }
1051     if(!strcmp(p, "$copy")) { // special case for copy selection
1052         CopySomething(clickedWord);
1053     } else
1054     if(!strcmp(p, "$chat")) { // special case for opening chat
1055         NewChat(clickedWord);
1056     } else
1057     if(q = strstr(p, "$input")) {
1058         if(!shellUp[TextMenuDlg]) return;
1059         strncpy(buf, p, MSG_SIZ);
1060         strncpy(buf + (q-p), q+6, MSG_SIZ-(q-p));
1061         PutText(buf, q-p);
1062     } else {
1063         snprintf(buf, MSG_SIZ, "%s\n", p);
1064         SendToICS(buf);
1065     }
1066     if(click) { // popped up by memo click
1067         click = clickedWord[0] = 0;
1068         PopDown(TextMenuDlg);
1069     }
1070 }
1071
1072 void
1073 IcsTextPopUp ()
1074 {
1075    int i=0, j;
1076    char *p, *q, *r;
1077    if((p = icsTextMenuString) == NULL) return;
1078    do {
1079         q = r = p; while(*p && *p != ';') p++;
1080         if(textOptions[i].name == NULL) textOptions[i].name = (char*) malloc(MSG_SIZ);
1081         for(j=0; j<p-q; j++) textOptions[i].name[j] = *r++;
1082         textOptions[i].name[j++] = 0;
1083         if(!*p) break;
1084         if(*++p == '\n') p++; // optional linefeed after button-text terminating semicolon
1085         q = p;
1086         textOptions[i].choice = (char**) (r = textOptions[i].name + j);
1087         while(*p && (*p != ';' || p[1] != '\n')) textOptions[i].name[j++] = *p++;
1088         textOptions[i].name[j++] = 0;
1089         if(*p) p += 2;
1090         textOptions[i].max = 135;
1091         textOptions[i].min = i&1;
1092         textOptions[i].handle = NULL;
1093         textOptions[i].target = &SendText;
1094         textOptions[i].textValue = strstr(r, "$input") ? "#80FF80" : strstr(r, "$name") ? "#FF8080" : "#FFFFFF";
1095         textOptions[i].type = Button;
1096    } while(++i < 99 && *p);
1097    if(i == 0) return;
1098    textOptions[i].type = EndMark;
1099    textOptions[i].target = NULL;
1100    textOptions[i].min = 2;
1101    MarkMenu("View.ICStextmenu", TextMenuDlg);
1102    GenericPopUp(textOptions, _("ICS text menu"), TextMenuDlg, BoardWindow, NONMODAL, appData.topLevel);
1103 }
1104
1105 void
1106 IcsTextProc ()
1107 {
1108     if(shellUp[TextMenuDlg]) PopDown(TextMenuDlg);
1109     else IcsTextPopUp();
1110 }
1111
1112 //---------------------------------------------------- Edit Comment -----------------------------------
1113
1114 static char *commentText;
1115 static int commentIndex;
1116 static void ClearComment P((int n));
1117 static void SaveChanges P((int n));
1118 int savedIndex;  /* gross that this is global (and even across files...) */
1119
1120 static int CommentClick P((Option *opt, int n, int x, int y, char *val, int index));
1121
1122 static int
1123 NewComCallback (int n)
1124 {
1125     ReplaceComment(commentIndex, commentText);
1126     return 1;
1127 }
1128
1129 Option commentOptions[] = {
1130 { 200, T_VSCRL | T_FILL | T_WRAP | T_TOP, 250, NULL, (void*) &commentText, NULL, (char **) &CommentClick, TextBox, "", &appData.commentFont },
1131 { 0,     0,     50, NULL, (void*) &ClearComment, NULL, NULL, Button, N_("clear") },
1132 { 0, SAME_ROW, 100, NULL, (void*) &SaveChanges, NULL, NULL, Button, N_("save changes") },
1133 { 0, SAME_ROW,  0,  NULL, (void*) &NewComCallback, "", NULL, EndMark , "" }
1134 };
1135
1136 static int
1137 CommentClick (Option *opt, int n, int x, int y, char *val, int index)
1138 {
1139         if(n != 3) return FALSE; // only button-3 press is of interest
1140         ReplaceComment(savedIndex, val);
1141         if(savedIndex != currentMove) ToNrEvent(savedIndex);
1142         LoadVariation( index, val ); // [HGM] also does the actual moving to it, now
1143         return TRUE;
1144 }
1145
1146 static void
1147 SaveChanges (int n)
1148 {
1149     GenericReadout(commentOptions, 0);
1150     ReplaceComment(commentIndex, commentText);
1151 }
1152
1153 static void
1154 ClearComment (int n)
1155 {
1156     SetWidgetText(&commentOptions[0], "", CommentDlg);
1157 }
1158
1159 void
1160 NewCommentPopup (char *title, char *text, int index)
1161 {
1162     if(DialogExists(CommentDlg)) { // if already exists, alter title and content
1163         SetDialogTitle(CommentDlg, title);
1164         SetWidgetText(&commentOptions[0], text, CommentDlg);
1165     }
1166     if(commentText) free(commentText); commentText = strdup(text);
1167     commentIndex = index;
1168     MarkMenu("View.Comments", CommentDlg);
1169     if(GenericPopUp(commentOptions, title, CommentDlg, BoardWindow, NONMODAL, appData.topLevel))
1170         AddHandler(&commentOptions[0], CommentDlg, 1);
1171 }
1172
1173 void
1174 EditCommentPopUp (int index, char *title, char *text)
1175 {
1176     savedIndex = index;
1177     if (text == NULL) text = "";
1178     NewCommentPopup(title, text, index);
1179 }
1180
1181 void
1182 CommentPopUp (char *title, char *text)
1183 {
1184     savedIndex = currentMove; // [HGM] vari
1185     NewCommentPopup(title, text, currentMove);
1186 }
1187
1188 void
1189 CommentPopDown ()
1190 {
1191     PopDown(CommentDlg);
1192 }
1193
1194
1195 void
1196 EditCommentProc ()
1197 {
1198     if (PopDown(CommentDlg)) { // popdown succesful
1199 //      MarkMenuItem("Edit.EditComment", False);
1200 //      MarkMenuItem("View.Comments", False);
1201     } else // was not up
1202         EditCommentEvent();
1203 }
1204
1205 //------------------------------------------------------ Edit Tags ----------------------------------
1206
1207 static void changeTags P((int n));
1208 static char *tagsText, **resPtr;
1209
1210 static int TagsClick P((Option *opt, int n, int x, int y, char *val, int index));
1211
1212 static int
1213 NewTagsCallback (int n)
1214 {
1215     if(bookUp) SaveToBook(tagsText), DisplayBook(currentMove); else
1216     if(resPtr) { ASSIGN(*resPtr, tagsText); } else
1217     ReplaceTags(tagsText, &gameInfo);
1218     return 1;
1219 }
1220
1221 static void
1222 NewMove ()
1223 {
1224     addToBookFlag = !addToBookFlag;
1225 }
1226
1227 Option tagsOptions[] = {
1228 {   0,   0,   0, NULL, NULL, NULL, NULL, Label,  NULL },
1229 { 200, T_VSCRL | T_FILL | T_TOP, 200, NULL, (void*) &tagsText, NULL, (char **) &TagsClick, TextBox, "", &appData.tagsFont },
1230 {   0,   0, 100, NULL, (void*) &NewMove,    NULL, NULL, Button, N_("add next move") },
1231 { 0,SAME_ROW,100,NULL, (void*) &changeTags, NULL, NULL, Button, N_("commit changes") },
1232 { 0,SAME_ROW, 0, NULL, (void*) &NewTagsCallback, "", NULL, EndMark , "" }
1233 };
1234
1235 static int TagsClick (Option *opt, int n, int x, int y, char *val, int index)
1236 {
1237     if(!bookUp || n != 3) return FALSE; // only button-3 press in Edit Book is of interest
1238     PlayBookMove(val, index);
1239     return TRUE;
1240 }
1241
1242 static void
1243 changeTags (int n)
1244 {
1245     GenericReadout(tagsOptions, 1);
1246     if(bookUp) SaveToBook(tagsText), DisplayBook(currentMove); else
1247     if(resPtr) { ASSIGN(*resPtr, tagsText); } else
1248     ReplaceTags(tagsText, &gameInfo);
1249 }
1250
1251 void
1252 NewTagsPopup (char *text, char *msg, char *ttl)
1253 {
1254     char *title = bookUp ? _("Edit book") : ttl;
1255
1256     tagsOptions[2].type = bookUp ? Button : Skip;
1257     tagsOptions[3].min = bookUp ? SAME_ROW : 0;
1258     if(DialogExists(TagsDlg)) { // if already exists, alter title and content
1259         SetWidgetText(&tagsOptions[1], text, TagsDlg);
1260         SetDialogTitle(TagsDlg, title);
1261     }
1262     if(tagsText) free(tagsText); tagsText = strdup(text);
1263     tagsOptions[0].name = msg;
1264     MarkMenu("View.Tags", TagsDlg);
1265     GenericPopUp(tagsOptions + (msg == NULL), title, TagsDlg, BoardWindow, NONMODAL, appData.topLevel);
1266 }
1267
1268 void
1269 TagsPopUp (char *tags, char *msg)
1270 {
1271     NewTagsPopup(tags, cmailMsgLoaded ? msg : NULL, _("Tags"));
1272 }
1273
1274 void
1275 EditTagsPopUp (char *tags, char **dest)
1276 {   // wrapper to preserve old name used in back-end
1277     resPtr = dest; 
1278     NewTagsPopup(tags, NULL, _("Tags"));
1279 }
1280
1281 void
1282 EditAnyPopUp (char *tags, char **dest, char *title)
1283 {   // wrapper to preserve old name used in back-end
1284     TagsPopDown();
1285     resPtr = dest; 
1286     NewTagsPopup(tags, NULL, title);
1287 }
1288
1289 void
1290 TagsPopDown()
1291 {
1292     PopDown(TagsDlg);
1293     bookUp = False;
1294 }
1295
1296 void
1297 EditTagsProc ()
1298 {
1299   if (bookUp || !PopDown(TagsDlg)) EditTagsEvent();
1300 }
1301
1302 void
1303 AddBookMove (char *text)
1304 {
1305     AppendText(&tagsOptions[1], text);
1306 }
1307
1308 //---------------------------------------------- ICS Input Box ----------------------------------
1309
1310 char *icsText;
1311
1312 // [HGM] code borrowed from winboard.c (which should thus go to backend.c!)
1313 #define HISTORY_SIZE 64
1314 static char *history[HISTORY_SIZE];
1315 static int histIn = 0, histP = 0;
1316 static Boolean noEcho;
1317
1318 static void
1319 SaveInHistory (char *cmd)
1320 {
1321   if(noEcho) return; // do not save password!
1322   if (history[histIn] != NULL) {
1323     free(history[histIn]);
1324     history[histIn] = NULL;
1325   }
1326   if (*cmd == NULLCHAR) return;
1327   history[histIn] = StrSave(cmd);
1328   histIn = (histIn + 1) % HISTORY_SIZE;
1329   if (history[histIn] != NULL) {
1330     free(history[histIn]);
1331     history[histIn] = NULL;
1332   }
1333   histP = histIn;
1334 }
1335
1336 static char *
1337 PrevInHistory (char *cmd)
1338 {
1339   int newhp;
1340   if (histP == histIn) {
1341     if (history[histIn] != NULL) free(history[histIn]);
1342     history[histIn] = StrSave(cmd);
1343   }
1344   newhp = (histP - 1 + HISTORY_SIZE) % HISTORY_SIZE;
1345   if (newhp == histIn || history[newhp] == NULL) return NULL;
1346   histP = newhp;
1347   return history[histP];
1348 }
1349
1350 static char *
1351 NextInHistory ()
1352 {
1353   if (histP == histIn) return NULL;
1354   histP = (histP + 1) % HISTORY_SIZE;
1355   return history[histP];
1356 }
1357 // end of borrowed code
1358
1359 #define INPUT 0
1360
1361 Option boxOptions[] = {
1362 {  30, T_TOP, 400, NULL, (void*) &icsText, NULL, NULL, TextBox, "" },
1363 {  0,  NO_OK,   0, NULL, NULL, "", NULL, EndMark , "" }
1364 };
1365
1366 void
1367 ICSInputSendText ()
1368 {
1369     char *val;
1370
1371     GetWidgetText(&boxOptions[INPUT], &val);
1372     SaveInHistory(val);
1373     SendMultiLineToICS(val);
1374     SetWidgetText(&boxOptions[INPUT], "", InputBoxDlg);
1375 }
1376
1377 void
1378 IcsKey (int n)
1379 {   // [HGM] input: let up-arrow recall previous line from history
1380     char *val = NULL; // to suppress spurious warning
1381
1382     if (!shellUp[InputBoxDlg]) return;
1383     switch(n) {
1384       case 0:
1385         ICSInputSendText();
1386         return;
1387       case 1:
1388         GetWidgetText(&boxOptions[INPUT], &val);
1389         val = PrevInHistory(val);
1390         break;
1391       case -1:
1392         val = NextInHistory();
1393     }
1394     SetWidgetText(&boxOptions[INPUT], val = val ? val : "", InputBoxDlg);
1395     SetInsertPos(&boxOptions[INPUT], strlen(val));
1396 }
1397
1398 void
1399 ICSInputBoxPopUp ()
1400 {
1401     MarkMenu("View.ICSInputBox", InputBoxDlg);
1402     if(GenericPopUp(boxOptions, _("ICS input box"), InputBoxDlg, BoardWindow, NONMODAL, 0))
1403         AddHandler(&boxOptions[INPUT], InputBoxDlg, 3);
1404     CursorAtEnd(&boxOptions[INPUT]);
1405 }
1406
1407 void
1408 IcsInputBoxProc ()
1409 {
1410     if (!PopDown(InputBoxDlg)) ICSInputBoxPopUp();
1411 }
1412
1413 //--------------------------------------------- Move Type In ------------------------------------------
1414
1415 static int TypeInOK P((int n));
1416
1417 Option typeOptions[] = {
1418 { 30, T_TOP, 400, NULL, (void*) &icsText, NULL, NULL, TextBox, "" },
1419 { 0,  NO_OK,   0, NULL, (void*) &TypeInOK, "", NULL, EndMark , "" }
1420 };
1421
1422 static int
1423 TypeInOK (int n)
1424 {
1425     TypeInDoneEvent(icsText);
1426     return TRUE;
1427 }
1428
1429 void
1430 PopUpMoveDialog (char firstchar)
1431 {
1432     static char buf[2];
1433     buf[0] = firstchar; ASSIGN(icsText, buf);
1434     if(GenericPopUp(typeOptions, _("Type a move"), TransientDlg, BoardWindow, MODAL, 0))
1435         AddHandler(&typeOptions[0], TransientDlg, 2);
1436     CursorAtEnd(&typeOptions[0]);
1437 }
1438
1439 void
1440 BoxAutoPopUp (char *buf)
1441 {       // only used in Xaw. GTK calls ConsoleAutoPopUp in stead (when we type to board)
1442         if(!appData.autoBox) return;
1443         if(appData.icsActive) { // text typed to board in ICS mode: divert to ICS input box
1444             if(DialogExists(InputBoxDlg)) { // box already exists: append to current contents
1445                 char *p, newText[MSG_SIZ];
1446                 GetWidgetText(&boxOptions[INPUT], &p);
1447                 snprintf(newText, MSG_SIZ, "%s%c", p, *buf);
1448                 SetWidgetText(&boxOptions[INPUT], newText, InputBoxDlg);
1449                 if(shellUp[InputBoxDlg]) HardSetFocus (&boxOptions[INPUT], InputBoxDlg); //why???
1450             } else icsText = buf; // box did not exist: make sure it pops up with char in it
1451             ICSInputBoxPopUp();
1452         } else PopUpMoveDialog(*buf);
1453 }
1454
1455 //------------------------------------------ Engine Settings ------------------------------------
1456
1457 void
1458 SettingsPopUp (ChessProgramState *cps)
1459 {
1460    if(!cps->nrOptions) { DisplayNote(_("Engine has no options")); return; }
1461    currentCps = cps;
1462    GenericPopUp(cps->option, _("Engine Settings"), TransientDlg, BoardWindow, MODAL, 0);
1463 }
1464
1465 void
1466 FirstSettingsProc ()
1467 {
1468     SettingsPopUp(&first);
1469 }
1470
1471 void
1472 SecondSettingsProc ()
1473 {
1474    if(WaitForEngine(&second, SettingsMenuIfReady)) return;
1475    SettingsPopUp(&second);
1476 }
1477
1478 void
1479 RefreshSettingsDialog (ChessProgramState *cps, int val)
1480 {
1481    if(val == 1) { // option values changed
1482       if(shellUp[TransientDlg] && cps == currentCps) {
1483          GenericUpdate(cps->option, -1); // normally update values when dialog is up
1484       }
1485       return; // and be done
1486    }
1487    if(val == 2) { // option list changed
1488       if(!shellUp[TransientDlg] || cps != currentCps) return; // our dialog is not up, so nothing to do
1489    }
1490    PopDown(TransientDlg); // make sure any other dialog closes first
1491    SettingsPopUp(cps);    // and popup new one
1492 }
1493
1494 //----------------------------------------------- Load Engine --------------------------------------
1495
1496 char *engineDir, *engineLine, *nickName, *params;
1497 Boolean isUCI, isUSI, hasBook, storeVariant, v1, addToList, useNick, secondEng;
1498
1499 static void EngSel P((int n, int sel));
1500 static int InstallOK P((int n));
1501
1502 static Option installOptions[] = {
1503 {   0,LR|T2T, 0, NULL, NULL, NULL, NULL, Label, N_("Select engine from list:") },
1504 { 300,LR|TB,200, NULL, (void*) engineMnemonic, (char*) &EngSel, NULL, ListBox, "" },
1505 { 0,SAME_ROW, 0, NULL, NULL, NULL, NULL, Break, NULL },
1506 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("or specify one below:") },
1507 {   0,  0,    0, NULL, (void*) &nickName, NULL, NULL, TextBox, N_("Nickname (optional):") },
1508 {   0,  0,    0, NULL, (void*) &useNick, NULL, NULL, CheckBox, N_("Use nickname in PGN player tags of engine-engine games") },
1509 {   0,  0,    0, NULL, (void*) &engineDir, NULL, NULL, PathName, N_("Engine Directory:") },
1510 {   0,  0,    0, NULL, (void*) &engineName, NULL, NULL, FileName, N_("Engine Command:") },
1511 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("(Directory will be derived from engine path when empty)") },
1512 {   0,  0,    0, NULL, (void*) &isUCI, NULL, NULL, CheckBox, N_("UCI") },
1513 {   0,  0,    0, NULL, (void*) &isUSI, NULL, NULL, CheckBox, N_("USI/UCCI (uses specified -uxiAdapter)") },
1514 {   0,  0,    0, NULL, (void*) &v1, NULL, NULL, CheckBox, N_("WB protocol v1 (do not wait for engine features)") },
1515 {   0,  0,    0, NULL, (void*) &hasBook, NULL, NULL, CheckBox, N_("Must not use GUI book") },
1516 {   0,  0,    0, NULL, (void*) &addToList, NULL, NULL, CheckBox, N_("Add this engine to the list") },
1517 {   0,  0,    0, NULL, (void*) &storeVariant, NULL, NULL, CheckBox, N_("Force current variant with this engine") },
1518 {   0,  0,    0, NULL, (void*) &InstallOK, "", NULL, EndMark , "" }
1519 };
1520
1521 static int
1522 InstallOK (int n)
1523 {
1524     if(n && (n = SelectedListBoxItem(&installOptions[1])) > 0) { // called by pressing OK, and engine selected
1525         ASSIGN(engineLine, engineList[n]);
1526     }
1527     PopDown(TransientDlg); // early popdown, to allow FreezeUI to instate grab
1528     if(isUSI) {
1529         isUCI = 2; // kludge to pass isUSI to Load()
1530         if(!*appData.ucciAdapter) { ASSIGN(appData.ucciAdapter, "usi2wb -%variant \"%fcp\"\"%fd\""); } // make sure -uxiAdapter is defined
1531     }
1532     if(!secondEng) Load(&first, 0); else Load(&second, 1);
1533     return FALSE; // no double PopDown!
1534 }
1535
1536 static void
1537 EngSel (int n, int sel)
1538 {
1539     int nr;
1540     char buf[MSG_SIZ];
1541     if(sel < 1) buf[0] = NULLCHAR; // back to top level
1542     else if(engineList[sel][0] == '#') safeStrCpy(buf, engineList[sel], MSG_SIZ); // group header, open group
1543     else { // normal line, select engine
1544         ASSIGN(engineLine, engineList[sel]);
1545         InstallOK(0);
1546         return;
1547     }
1548     nr = NamesToList(firstChessProgramNames, engineList, engineMnemonic, buf); // replace list by only the group contents
1549     ASSIGN(engineMnemonic[0], buf);
1550     LoadListBox(&installOptions[1], _("# no engines are installed"), -1, -1);
1551     HighlightWithScroll(&installOptions[1], 0, nr);
1552 }
1553
1554 static void
1555 LoadEngineProc (int engineNr, char *title)
1556 {
1557    isUCI = isUSI = storeVariant = v1 = useNick = False; addToList = hasBook = True; // defaults
1558    secondEng = engineNr;
1559    if(engineLine)   free(engineLine);   engineLine = strdup("");
1560    if(engineDir)    free(engineDir);    engineDir = strdup(".");
1561    if(nickName)     free(nickName);     nickName = strdup("");
1562    if(params)       free(params);       params = strdup("");
1563    ASSIGN(engineMnemonic[0], "");
1564    NamesToList(firstChessProgramNames, engineList, engineMnemonic, "");
1565    GenericPopUp(installOptions, title, TransientDlg, BoardWindow, MODAL, 0);
1566 }
1567
1568 void
1569 LoadEngine1Proc ()
1570 {
1571     LoadEngineProc (0, _("Load first engine"));
1572 }
1573
1574 void
1575 LoadEngine2Proc ()
1576 {
1577     LoadEngineProc (1, _("Load second engine"));
1578 }
1579
1580 //----------------------------------------------------- Edit Book -----------------------------------------
1581
1582 void
1583 EditBookProc ()
1584 {
1585     EditBookEvent();
1586 }
1587
1588 //--------------------------------------------------- New Shuffle Game ------------------------------
1589
1590 static void SetRandom P((int n));
1591
1592 static int
1593 ShuffleOK (int n)
1594 {
1595     ResetGameEvent();
1596     return 1;
1597 }
1598
1599 static Option shuffleOptions[] = {
1600   {   0,  0,    0, NULL, (void*) &shuffleOpenings, NULL, NULL, CheckBox, N_("shuffle") },
1601   {   0,  0,    0, NULL, (void*) &appData.fischerCastling, NULL, NULL, CheckBox, N_("Fischer castling") },
1602   { 0,-1,2000000000, NULL, (void*) &appData.defaultFrcPosition, "", NULL, Spin, N_("Start-position number:") },
1603   {   0,  0,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("randomize") },
1604   {   0,  SAME_ROW,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("pick fixed") },
1605   { 0,SAME_ROW, 0, NULL, (void*) &ShuffleOK, "", NULL, EndMark , "" }
1606 };
1607
1608 static void
1609 SetRandom (int n)
1610 {
1611     int r = n==3 ? -1 : random() & (1<<30)-1;
1612     char buf[MSG_SIZ];
1613     snprintf(buf, MSG_SIZ,  "%d", r);
1614     SetWidgetText(&shuffleOptions[2], buf, TransientDlg);
1615     SetWidgetState(&shuffleOptions[0], True);
1616 }
1617
1618 void
1619 ShuffleMenuProc ()
1620 {
1621     GenericPopUp(shuffleOptions, _("New Shuffle Game"), TransientDlg, BoardWindow, MODAL, 0);
1622 }
1623
1624 //--------------------------------------------------- Fonts ------------------------------
1625
1626 static void AdjustFont P((int n));
1627
1628 static char *oldFont[7];
1629
1630 static int
1631 NewFont (int n, int fnr, char *font)
1632 {   // figure out if font changed, and if so, store it in the fonts table as a side effect
1633     if(!strcmp(oldFont[n], font)) return 0; // not changed
1634     ASSIGN(fontTable[fnr][initialSquareSize], font);
1635     fontIsSet[fnr] = fontValid[fnr][initialSquareSize] = True;
1636     return 1; // changed
1637 }
1638
1639 static int
1640 FontsOK (int n)
1641 {
1642     int i;
1643     PopDown(TransientDlg); // Early popdown to prevent expose events frommasking each other
1644     LockBoardSize(0);
1645     if(NewFont(0, CLOCK_FONT,   appData.clockFont)) DisplayBothClocks();
1646     if(NewFont(1, MESSAGE_FONT, appData.font)) {
1647         ApplyFont(&mainOptions[W_MESSG], NULL);
1648         for(i=1; i<6; i++) ApplyFont(&mainOptions[W_BUTTON+i], NULL);
1649     }
1650     LockBoardSize(1); // unlock
1651     if(NewFont(3, EDITTAGS_FONT,    appData.tagsFont))    ApplyFont(&tagsOptions[1], NULL);
1652     if(NewFont(4, COMMENT_FONT,     appData.commentFont)) ApplyFont(&commentOptions[0], NULL);
1653     if(NewFont(5, MOVEHISTORY_FONT, appData.historyFont)) {
1654         ApplyFont(&historyOptions[0], NULL);
1655         ApplyFont(&engoutOptions[5], NULL);
1656         ApplyFont(&engoutOptions[12], NULL);
1657     }
1658     if(NewFont(6, GAMELIST_FONT, appData.gameListFont)) ApplyFont(&gamesOptions[0], NULL);
1659     if(NewFont(2, CONSOLE_FONT,  appData.icsFont)) {
1660         ApplyFont(&chatOptions[11], appData.icsFont);
1661         AppendColorized(&chatOptions[6], NULL, 0); // kludge to replace font tag
1662     }
1663     DrawPosition(TRUE, NULL); // for coord font
1664     return 0; // suppress normal popdown because already done
1665 }
1666
1667 static Option fontOptions[] = {
1668   { 0,        60, 200, NULL, (void*) &appData.clockFont, NULL, NULL, TextBox, N_("Clocks (requires restart):") },
1669   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1670   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1671   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1672   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1673   {  666, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("*") },
1674   { 0,         60, 70, NULL, (void*) &appData.font, NULL, NULL, TextBox, N_("Message (above board):") },
1675   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1676   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1677   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1678   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1679   {  666, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("*") },
1680   { 0,         60, 70, NULL, (void*) &appData.icsFont, NULL, NULL, TextBox, N_("ICS Chat/Console:") },
1681   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1682   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1683   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1684   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1685   {  666, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("*") },
1686   { 0,         60, 70, NULL, (void*) &appData.tagsFont, NULL, NULL, TextBox, N_("Edit tags / book / engine list:") },
1687   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1688   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1689   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1690   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1691   {  666, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("*") },
1692   { 0,         60, 70, NULL, (void*) &appData.commentFont, NULL, NULL, TextBox, N_("Edit comments:") },
1693   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1694   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1695   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1696   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1697   {  666, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("*") },
1698   { 0,         60, 70, NULL, (void*) &appData.historyFont, NULL, NULL, TextBox, N_("Move history / Engine Output:") },
1699   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1700   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1701   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1702   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1703   {  666, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("*") },
1704   { 0,         60, 70, NULL, (void*) &appData.gameListFont, NULL, NULL, TextBox, N_("Game list:") },
1705   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1706   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1707   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1708   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1709   {  666, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("*") },
1710   {   0,  0,    0, NULL, NULL, NULL, NULL, Label, N_("\nThe * buttons will set the font to the one selected below:") },
1711   {   0,  0,    0, NULL, NULL, NULL, NULL, Button, "fontsel" },
1712   { 0, 0, 0, NULL, (void*) &FontsOK, "", NULL, EndMark , "" }
1713 };
1714
1715 static char name[MSG_SIZ], *bold, *ital, points;
1716
1717 static void
1718 BreakUp (char *font)
1719 {
1720     char *p = name, *norm;
1721     safeStrCpy(name, font, MSG_SIZ);
1722     bold = StrCaseStr(name, "bold");
1723     ital = StrCaseStr(name, "ital");
1724     norm = StrCaseStr(name, "normal");
1725     points = 0;
1726     while(p && *p && !(points = atoi(p))) p = strchr(p+1, ' ');
1727     if(points) p[*p == ' '] = 0;
1728     if(bold) *bold = 0;
1729     if(ital) *ital = 0;
1730     if(norm) *norm = 0;
1731 }
1732
1733 static void
1734 Collect ()
1735 {
1736     if(bold) strcat(name, "Bold ");
1737     if(ital) strcat(name, "Italic ");
1738     if(!ital && !bold && strlen(name) < 2) strncpy(name, "Normal ", MSG_SIZ);
1739     if(points) sprintf(name + strlen(name), "%d", points); else strcat(name, "%d");
1740 }
1741
1742 static void
1743 AdjustFont (int n)
1744 {
1745     int button = fontOptions[n].value, base = n - button;
1746     char *oldFont;
1747     GetWidgetText(&fontOptions[base], &oldFont);
1748     BreakUp(oldFont); // take apart old font name
1749     switch(button) {
1750       case 1: points++; break;
1751       case 2: points--; break;
1752       case 3: if(bold) bold = NULL; else bold = name; break;
1753       case 4: if(ital) ital = NULL; else ital = name; break;
1754     }
1755     Collect();
1756     SetWidgetText(&fontOptions[base], name, TransientDlg);
1757     ApplyFont(&fontOptions[base], name);
1758 }
1759
1760 void
1761 FontsProc ()
1762 {
1763     int i;
1764     if(strstr(appData.font, "-*-")) { DisplayNote(_("This only works in the GTK build")); return; }
1765     GenericPopUp(fontOptions, _("Fonts"), TransientDlg, BoardWindow, MODAL, 0);
1766     for(i=0; i<7; i++) {
1767         ApplyFont(&fontOptions[6*i], *(char**)fontOptions[6*i].target);
1768         ASSIGN(oldFont[i], *(char**)fontOptions[6*i].target);
1769     }
1770 }
1771
1772 //------------------------------------------------------ Time Control -----------------------------------
1773
1774 static int TcOK P((int n));
1775 int tmpMoves, tmpTc, tmpInc, tmpOdds1, tmpOdds2, tcType, by60;
1776
1777 static void SetTcType P((int n));
1778
1779 static char *
1780 Value (int n)
1781 {
1782         static char buf[MSG_SIZ];
1783         snprintf(buf, MSG_SIZ, "%d", n);
1784         return buf;
1785 }
1786
1787 static Option tcOptions[] = {
1788 {   0,  0,    0, NULL, (void*) &SetTcType, NULL, NULL, Button, N_("classical") },
1789 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("incremental") },
1790 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("fixed max") },
1791 {   0,  0,    0, NULL, (void*) &by60,     "",  NULL, CheckBox, N_("Divide entered times by 60") },
1792 {   0,  0,  200, NULL, (void*) &tmpMoves, NULL, NULL, Spin, N_("Moves per session:") },
1793 {   0,  0,10000, NULL, (void*) &tmpTc,    NULL, NULL, Spin, N_("Initial time (min):") },
1794 {   0, 0, 10000, NULL, (void*) &tmpInc,   NULL, NULL, Spin, N_("Increment or max (sec/move):") },
1795 {   0,  0,    0, NULL, NULL, NULL, NULL, Label, N_("Time-Odds factors:") },
1796 {   0,  1, 1000, NULL, (void*) &tmpOdds1, NULL, NULL, Spin, N_("Engine #1") },
1797 {   0,  1, 1000, NULL, (void*) &tmpOdds2, NULL, NULL, Spin, N_("Engine #2 / Human") },
1798 {   0,  0,    0, NULL, (void*) &TcOK, "", NULL, EndMark , "" }
1799 };
1800
1801 static int
1802 TcOK (int n)
1803 {
1804     char *tc, buf[MSG_SIZ];
1805     if(tcType == 0 && tmpMoves <= 0) return 0;
1806     if(tcType == 2 && tmpInc <= 0) return 0;
1807     GetWidgetText(&tcOptions[5], &tc); // get original text, in case it is min:sec
1808     if(by60) snprintf(buf, MSG_SIZ, "%d:%02d", tmpTc/60, tmpTc%60), tc=buf;
1809     searchTime = 0;
1810     switch(tcType) {
1811       case 0:
1812         if(!ParseTimeControl(tc, -1, tmpMoves)) return 0;
1813         appData.movesPerSession = tmpMoves;
1814         ASSIGN(appData.timeControl, tc);
1815         appData.timeIncrement = -1;
1816         break;
1817       case 1:
1818         if(!ParseTimeControl(tc, tmpInc, 0)) return 0;
1819         ASSIGN(appData.timeControl, tc);
1820         appData.timeIncrement = (by60 ? tmpInc/60. : tmpInc);
1821         break;
1822       case 2:
1823         searchTime = (by60 ? tmpInc/60 : tmpInc);
1824     }
1825     appData.firstTimeOdds = first.timeOdds = tmpOdds1;
1826     appData.secondTimeOdds = second.timeOdds = tmpOdds2;
1827     Reset(True, True);
1828     return 1;
1829 }
1830
1831 static void
1832 SetTcType (int n)
1833 {
1834     switch(tcType = n) {
1835       case 0:
1836         SetWidgetText(&tcOptions[4], Value(tmpMoves), TransientDlg);
1837         SetWidgetText(&tcOptions[5], Value(tmpTc), TransientDlg);
1838         SetWidgetText(&tcOptions[6], _("Unused"), TransientDlg);
1839         break;
1840       case 1:
1841         SetWidgetText(&tcOptions[4], _("Unused"), TransientDlg);
1842         SetWidgetText(&tcOptions[5], Value(tmpTc), TransientDlg);
1843         SetWidgetText(&tcOptions[6], Value(tmpInc), TransientDlg);
1844         break;
1845       case 2:
1846         SetWidgetText(&tcOptions[4], _("Unused"), TransientDlg);
1847         SetWidgetText(&tcOptions[5], _("Unused"), TransientDlg);
1848         SetWidgetText(&tcOptions[6], Value(tmpInc), TransientDlg);
1849     }
1850 }
1851
1852 void
1853 TimeControlProc ()
1854 {
1855    if(gameMode != BeginningOfGame) {
1856         DisplayError(_("Changing time control during a game is not implemented"), 0);
1857         return;
1858    }
1859    tmpMoves = appData.movesPerSession;
1860    tmpInc = appData.timeIncrement; if(tmpInc < 0) tmpInc = 0;
1861    tmpOdds1 = tmpOdds2 = 1; tcType = 0;
1862    tmpTc = atoi(appData.timeControl);
1863    by60 = 0;
1864    GenericPopUp(tcOptions, _("Time Control"), TransientDlg, BoardWindow, MODAL, 0);
1865    SetTcType(searchTime ? 2 : appData.timeIncrement < 0 ? 0 : 1);
1866 }
1867
1868 //------------------------------- Ask Question -----------------------------------------
1869
1870 int SendReply P((int n));
1871 char pendingReplyPrefix[MSG_SIZ];
1872 ProcRef pendingReplyPR;
1873 char *answer;
1874
1875 Option askOptions[] = {
1876 { 0, 0, 0, NULL, NULL, NULL, NULL, Label,  NULL },
1877 { 0, 0, 0, NULL, (void*) &answer, "", NULL, TextBox, "" },
1878 { 0, 0, 0, NULL, (void*) &SendReply, "", NULL, EndMark , "" }
1879 };
1880
1881 int
1882 SendReply (int n)
1883 {
1884     char buf[MSG_SIZ];
1885     int err;
1886     char *reply=answer;
1887 //    GetWidgetText(&askOptions[1], &reply);
1888     safeStrCpy(buf, pendingReplyPrefix, sizeof(buf)/sizeof(buf[0]) );
1889     if (*buf) strncat(buf, " ", MSG_SIZ - strlen(buf) - 1);
1890     strncat(buf, reply, MSG_SIZ - strlen(buf) - 1);
1891     strncat(buf, "\n",  MSG_SIZ - strlen(buf) - 1);
1892     OutputToProcess(pendingReplyPR, buf, strlen(buf), &err); // does not go into debug file??? => bug
1893     if (err) DisplayFatalError(_("Error writing to chess program"), err, 0);
1894     return TRUE;
1895 }
1896
1897 void
1898 AskQuestion (char *title, char *question, char *replyPrefix, ProcRef pr)
1899 {
1900     safeStrCpy(pendingReplyPrefix, replyPrefix, sizeof(pendingReplyPrefix)/sizeof(pendingReplyPrefix[0]) );
1901     pendingReplyPR = pr;
1902     ASSIGN(answer, "");
1903     askOptions[0].name = question;
1904     if(GenericPopUp(askOptions, title, AskDlg, BoardWindow, MODAL, 0))
1905         AddHandler(&askOptions[1], AskDlg, 2);
1906 }
1907
1908 //---------------------------- Promotion Popup --------------------------------------
1909
1910 static int count;
1911
1912 static void PromoPick P((int n));
1913
1914 static Option promoOptions[] = {
1915 {   0,         0,    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,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1923 {   0, SAME_ROW | NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
1924 };
1925
1926 static void
1927 PromoPick (int n)
1928 {
1929     int promoChar = promoOptions[n+count].value;
1930
1931     PopDown(PromoDlg);
1932
1933     if (promoChar == 0) fromX = -1;
1934     if (fromX == -1) return;
1935
1936     if (! promoChar) {
1937         fromX = fromY = -1;
1938         ClearHighlights();
1939         return;
1940     }
1941     if(promoChar == '=' && !IS_SHOGI(gameInfo.variant)) promoChar = NULLCHAR;
1942     UserMoveEvent(fromX, fromY, toX, toY, promoChar);
1943
1944     if (!appData.highlightLastMove || gotPremove) ClearHighlights();
1945     if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
1946     fromX = fromY = -1;
1947 }
1948
1949 static void
1950 SetPromo (char *name, int nr, char promoChar)
1951 {
1952     ASSIGN(promoOptions[nr].name, name);
1953     promoOptions[nr].value = promoChar;
1954     promoOptions[nr].min = SAME_ROW;
1955 }
1956
1957 void
1958 PromotionPopUp (char choice)
1959 { // choice depends on variant: prepare dialog acordingly
1960   count = 8;
1961   SetPromo(_("Cancel"), --count, -1); // Beware: GenericPopUp cannot handle user buttons named "cancel" (lowe case)!
1962   if(choice != '+' && !IS_SHOGI(gameInfo.variant)) {
1963     if (!appData.testLegality || gameInfo.variant == VariantSuicide ||
1964         gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove) ||
1965         gameInfo.variant == VariantGiveaway) {
1966       SetPromo(_("King"), --count, 'k');
1967     }
1968     if(gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove)) {
1969       SetPromo(_("Captain"), --count, 'c');
1970       SetPromo(_("Lieutenant"), --count, 'l');
1971       SetPromo(_("General"), --count, 'g');
1972       SetPromo(_("Warlord"), --count, 'w');
1973     } else {
1974       SetPromo(_("Knight"), --count, 'n');
1975       SetPromo(_("Bishop"), --count, 'b');
1976       SetPromo(_("Rook"), --count, 'r');
1977       if(gameInfo.variant == VariantCapablanca ||
1978          gameInfo.variant == VariantGothic ||
1979          gameInfo.variant == VariantCapaRandom) {
1980         SetPromo(_("Archbishop"), --count, 'a');
1981         SetPromo(_("Chancellor"), --count, 'c');
1982       }
1983       SetPromo(_("Queen"), --count, 'q');
1984       if(gameInfo.variant == VariantChuChess)
1985         SetPromo(_("Lion"), --count, 'l');
1986     }
1987   } else // [HGM] shogi
1988   {
1989       SetPromo(_("Defer"), --count, '=');
1990       SetPromo(_("Promote"), --count, '+');
1991   }
1992   promoOptions[count].min = 0;
1993   GenericPopUp(promoOptions + count, "Promotion", PromoDlg, BoardWindow, NONMODAL, 0);
1994 }
1995
1996 //---------------------------- Chat Windows ----------------------------------------------
1997
1998 static char *line, *memo, *chatMemo, *partner, *texts[MAX_CHAT], dirty[MAX_CHAT], *inputs[MAX_CHAT], *icsLine, *tmpLine;
1999 static int activePartner;
2000 int hidden = 1;
2001
2002 void ChatSwitch P((int n));
2003 int  ChatOK P((int n));
2004
2005 #define CHAT_ICS     6
2006 #define CHAT_PARTNER 8
2007 #define CHAT_OUT    11
2008 #define CHAT_PANE   12
2009 #define CHAT_IN     13
2010
2011 void PaneSwitch P((void));
2012 void ClearChat P((void));
2013
2014 WindowPlacement wpTextMenu;
2015
2016 int
2017 ContextMenu (Option *opt, int button, int x, int y, char *text, int index)
2018 { // callback for ICS-output clicks; handles button 3, passes on other events
2019   int h;
2020   if(button == -3) return TRUE; // supress default GTK context menu on up-click
2021   if(button != 3) return FALSE;
2022   if(index == -1) { // pre-existing selection in memo
2023     strncpy(clickedWord, text, MSG_SIZ);
2024   } else { // figure out what word was clicked
2025     char *start, *end;
2026     start = end = text + index;
2027     while(isalnum(*end)) end++;
2028     while(start > text && isalnum(start[-1])) start--;
2029     clickedWord[0] = NULLCHAR;
2030     if(end-start >= 80) end = start + 80; // intended for small words and numbers
2031     strncpy(clickedWord, start, end-start); clickedWord[end-start] = NULLCHAR;
2032   }
2033   click = !shellUp[TextMenuDlg]; // request auto-popdown of textmenu when we popped it up
2034   h = wpTextMenu.height; // remembered height of text menu
2035   if(h <= 0) h = 65;     // when not available, position w.r.t. top
2036   GetPlacement(ChatDlg, &wpTextMenu);
2037   if(opt->target == (void*) &chatMemo) wpTextMenu.y += (wpTextMenu.height - 30)/2; // click in chat
2038   wpTextMenu.x += x - 50; wpTextMenu.y += y - h + 50; // request positioning
2039   if(wpTextMenu.x < 0) wpTextMenu.x = 0;
2040   if(wpTextMenu.y < 0) wpTextMenu.y = 0;
2041   wpTextMenu.width = wpTextMenu.height = -1;
2042   IcsTextPopUp();
2043   return TRUE;
2044 }
2045
2046 Option chatOptions[] = {
2047 {  0,  0,   0, NULL, NULL, NULL, NULL, Label , N_("Chats:") },
2048 { 1, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
2049 { 2, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
2050 { 3, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
2051 { 4, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
2052 { 5, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
2053 { 250, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &memo, NULL, (void*) &ContextMenu, TextBox, "" },
2054 {  0,  0,   0, NULL, NULL, "", NULL, Break , "" },
2055 { 0,   T_TOP,    100, NULL, (void*) &partner, NULL, NULL, TextBox, N_("Chat partner:") },
2056 {  0, SAME_ROW, 0, NULL, (void*) &ClearChat,  NULL, NULL, Button, N_("End Chat") },
2057 {  0, SAME_ROW, 0, NULL, (void*) &PaneSwitch, NULL, NULL, Button, N_("Hide") },
2058 { 250, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &chatMemo, NULL, (void*) &ContextMenu, TextBox, "" },
2059 {  0,  0,   0, NULL, NULL, "", NULL, Break , "" },
2060 {  0,    0,  510, NULL, (void*) &line, NULL, NULL, TextBox, "" },
2061 { 0, NO_OK|SAME_ROW, 0, NULL, (void*) &ChatOK, NULL, NULL, EndMark , "" }
2062 };
2063
2064 static void
2065 PutText (char *text, int pos)
2066 {
2067     char buf[MSG_SIZ], *p;
2068     DialogClass dlg = ChatDlg;
2069     Option *opt = &chatOptions[CHAT_IN];
2070
2071     if(strstr(text, "$add ") == text) {
2072         GetWidgetText(&boxOptions[INPUT], &p);
2073         snprintf(buf, MSG_SIZ, "%s%s", p, text+5); text = buf;
2074         pos += strlen(p) - 5;
2075     }
2076     if(shellUp[InputBoxDlg]) opt = &boxOptions[INPUT], dlg = InputBoxDlg; // for the benefit of Xaw give priority to ICS Input Box
2077     SetWidgetText(opt, text, dlg);
2078     SetInsertPos(opt, pos);
2079     HardSetFocus(opt, dlg);
2080     CursorAtEnd(opt);
2081 }
2082
2083 int
2084 IcsHist (int n, Option *opt, DialogClass dlg)
2085 {   // [HGM] input: let up-arrow recall previous line from history
2086     char *val = NULL; // to suppress spurious warning
2087     int chat, start;
2088
2089     if(opt != &chatOptions[CHAT_IN] && !(opt == &chatOptions[CHAT_PARTNER] && n == 33)) return 0;
2090     switch(n) {
2091       case 5:
2092         if(!hidden) ClearChat();
2093         break;
2094       case 8:
2095         if(!hidden) PaneSwitch();
2096         break;
2097       case 33: // <Esc>
2098         if(1) BoardToTop(); else
2099         if(hidden) BoardToTop();
2100         else PaneSwitch();
2101         break;
2102       case 15:
2103         NewChat(lastTalker);
2104         break;
2105       case 14:
2106         for(chat=0; chat < MAX_CHAT; chat++) if(!chatPartner[chat][0]) break;
2107         if(chat < MAX_CHAT) ChatSwitch(chat + 1);
2108         break;
2109       case 10: // <Tab>
2110         chat = start = (activePartner - hidden + MAX_CHAT) % MAX_CHAT;
2111         while(!dirty[chat = (chat + 1)%MAX_CHAT]) if(chat == start) break;
2112         if(!dirty[chat])
2113         while(!chatPartner[chat = (chat + 1)%MAX_CHAT][0]) if(chat == start) break;
2114         if(!chatPartner[chat][0]) break; // if all unused, ignore
2115         ChatSwitch(chat + 1);
2116         break;
2117       case 1:
2118         GetWidgetText(opt, &val);
2119         val = PrevInHistory(val);
2120         break;
2121       case -1:
2122         val = NextInHistory();
2123     }
2124     SetWidgetText(opt, val = val ? val : "", dlg);
2125     SetInsertPos(opt, strlen(val));
2126     return 1;
2127 }
2128
2129 void
2130 OutputChatMessage (int partner, char *mess)
2131 {
2132     char *p = texts[partner];
2133     int len = strlen(mess) + 1;
2134
2135     if(!DialogExists(ChatDlg)) return;
2136     if(p) len += strlen(p);
2137     texts[partner] = (char*) malloc(len);
2138     snprintf(texts[partner], len, "%s%s", p ? p : "", mess);
2139     FREE(p);
2140     if(partner == activePartner && !hidden) {
2141         AppendText(&chatOptions[CHAT_OUT], mess);
2142         SetInsertPos(&chatOptions[CHAT_OUT], len-2);
2143     } else {
2144         SetColor("#FFC000", &chatOptions[partner + 1]);
2145         dirty[partner] = 1;
2146     }
2147 }
2148
2149 int
2150 ChatOK (int n)
2151 {   // can only be called through <Enter> in chat-partner text-edit, as there is no OK button
2152     char buf[MSG_SIZ];
2153
2154     if(!hidden && (!partner || strcmp(partner, chatPartner[activePartner]) || !*partner)) {
2155         safeStrCpy(chatPartner[activePartner], partner, MSG_SIZ);
2156         SetWidgetText(&chatOptions[CHAT_OUT], "", -1); // clear text if we alter partner
2157         SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg); // clear text if we alter partner
2158         SetWidgetLabel(&chatOptions[activePartner+1], chatPartner[activePartner][0] ? chatPartner[activePartner] : _("New Chat"));
2159         if(!*partner) PaneSwitch();
2160         HardSetFocus(&chatOptions[CHAT_IN], 0);
2161     }
2162     if(line[0] || hidden) { // something was typed (for ICS commands we also allow empty line!)
2163         SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg);
2164         // from here on it could be back-end
2165         if(line[strlen(line)-1] == '\n') line[strlen(line)-1] = NULLCHAR;
2166         SaveInHistory(line);
2167         if(hidden || !*chatPartner[activePartner]) snprintf(buf, MSG_SIZ, "%s\n", line); else // command for ICS
2168         if(!strcmp("whispers", chatPartner[activePartner]))
2169               snprintf(buf, MSG_SIZ, "whisper %s\n", line); // WHISPER box uses "whisper" to send
2170         else if(!strcmp("shouts", chatPartner[activePartner]))
2171               snprintf(buf, MSG_SIZ, "shout %s\n", line); // SHOUT box uses "shout" to send
2172         else if(!strcmp("c-shouts", chatPartner[activePartner]))
2173               snprintf(buf, MSG_SIZ, "cshout %s\n", line); // C-SHOUT box uses "cshout" to send
2174         else if(!strcmp("kibitzes", chatPartner[activePartner]))
2175               snprintf(buf, MSG_SIZ, "kibitz %s\n", line); // KIBITZ box uses "kibitz" to send
2176         else {
2177             if(!atoi(chatPartner[activePartner])) {
2178                 snprintf(buf, MSG_SIZ, "> %s\n", line); // echo only tells to handle, not channel
2179                 OutputChatMessage(activePartner, buf);
2180                 snprintf(buf, MSG_SIZ, "xtell %s %s\n", chatPartner[activePartner], line);
2181             } else
2182                 snprintf(buf, MSG_SIZ, "tell %s %s\n", chatPartner[activePartner], line);
2183         }
2184         SendToICS(buf);
2185     }
2186     return FALSE; // never pop down
2187 }
2188
2189 void
2190 DelayedSetText ()
2191 {
2192     SetWidgetText(&chatOptions[CHAT_IN], tmpLine, -1); // leave focus on chat-partner field!
2193     SetInsertPos(&chatOptions[CHAT_IN], strlen(tmpLine));
2194 }
2195
2196 void
2197 DelayedScroll ()
2198 {   // If we do this immediately it does it before shrinking the memo, so the lower half remains hidden (Ughh!)
2199     SetInsertPos(&chatOptions[CHAT_ICS], 999999);
2200     SetWidgetText(&chatOptions[CHAT_IN], tmpLine, ChatDlg);
2201     SetInsertPos(&chatOptions[CHAT_IN], strlen(tmpLine));
2202 }
2203
2204 void
2205 ChatSwitch (int n)
2206 {
2207     int i, j;
2208     char *v;
2209     if(chatOptions[CHAT_ICS].type == Skip) hidden = 0; // In Xaw there is no ICS pane we can hide behind
2210     Show(&chatOptions[CHAT_PANE], 0); // show
2211     if(hidden) ScheduleDelayedEvent(DelayedScroll, 50); // Awful!
2212     else ScheduleDelayedEvent(DelayedSetText, 50);
2213     GetWidgetText(&chatOptions[CHAT_IN], &v);
2214     if(hidden) { ASSIGN(icsLine, v); } else { ASSIGN(inputs[activePartner], v); }
2215     hidden = 0;
2216     activePartner = --n;
2217     if(!texts[n]) texts[n] = strdup("");
2218     dirty[n] = 0;
2219     SetWidgetText(&chatOptions[CHAT_OUT], texts[n], ChatDlg);
2220     SetInsertPos(&chatOptions[CHAT_OUT], strlen(texts[n]));
2221     SetWidgetText(&chatOptions[CHAT_PARTNER], chatPartner[n], ChatDlg);
2222     for(i=j=0; i<MAX_CHAT; i++) {
2223         SetWidgetLabel(&chatOptions[++j], *chatPartner[i] ? chatPartner[i] : _("New Chat"));
2224         SetColor(dirty[i] ? "#FFC000" : "#FFFFFF", &chatOptions[j]);
2225     }
2226     if(!inputs[n]) { ASSIGN(inputs[n], ""); }
2227 //    SetWidgetText(&chatOptions[CHAT_IN], inputs[n], ChatDlg); // does not work (in this widget only)
2228 //    SetInsertPos(&chatOptions[CHAT_IN], strlen(inputs[n]));
2229     tmpLine = inputs[n]; // for the delayed event
2230     HardSetFocus(&chatOptions[strcmp(chatPartner[n], "") ? CHAT_IN : CHAT_PARTNER], 0);
2231 }
2232
2233 void
2234 PaneSwitch ()
2235 {
2236     char *v;
2237     Show(&chatOptions[CHAT_PANE], hidden = 1); // hide
2238     GetWidgetText(&chatOptions[CHAT_IN], &v);
2239     ASSIGN(inputs[activePartner], v);
2240     if(!icsLine) { ASSIGN(icsLine, ""); }
2241     tmpLine = icsLine; ScheduleDelayedEvent(DelayedSetText, 50);
2242 //    SetWidgetText(&chatOptions[CHAT_IN], icsLine, ChatDlg); // does not work (in this widget only)
2243 //    SetInsertPos(&chatOptions[CHAT_IN], strlen(icsLine));
2244 }
2245
2246 void
2247 ClearChat ()
2248 {   // clear the chat to make it free for other use
2249     chatPartner[activePartner][0] = NULLCHAR;
2250     ASSIGN(texts[activePartner], "");
2251     ASSIGN(inputs[activePartner], "");
2252     SetWidgetText(&chatOptions[CHAT_PARTNER], "", ChatDlg);
2253     SetWidgetText(&chatOptions[CHAT_OUT], "", ChatDlg);
2254     SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg);
2255     SetWidgetLabel(&chatOptions[activePartner+1], _("New Chat"));
2256     HardSetFocus(&chatOptions[CHAT_PARTNER], 0);
2257 }
2258
2259 static void
2260 NewChat (char *name)
2261 {   // open a chat on program request. If no empty one available, use last
2262     int i;
2263     for(i=0; i<MAX_CHAT-1; i++) if(!chatPartner[i][0]) break;
2264     safeStrCpy(chatPartner[i], name, MSG_SIZ);
2265     ChatSwitch(i+1);
2266 }
2267
2268 void
2269 ConsoleWrite(char *message, int count)
2270 {
2271     if(shellUp[ChatDlg] && chatOptions[CHAT_ICS].type != Skip) { // in Xaw this is a no-op
2272         if(*message == 7) {
2273             message++; // remove bell
2274             if(strcmp(message, "\n")) return;
2275         }
2276         AppendColorized(&chatOptions[CHAT_ICS], message, count);
2277         SetInsertPos(&chatOptions[CHAT_ICS], 999999);
2278     }
2279 }
2280
2281 void
2282 ChatPopUp ()
2283 {
2284     if(GenericPopUp(chatOptions, _("ICS Interaction"), ChatDlg, BoardWindow, NONMODAL, appData.topLevel))
2285         AddHandler(&chatOptions[CHAT_PARTNER], ChatDlg, 2), AddHandler(&chatOptions[CHAT_IN], ChatDlg, 2); // treats return as OK
2286     Show(&chatOptions[CHAT_PANE], hidden = 1); // hide
2287 //    HardSetFocus(&chatOptions[CHAT_IN], 0);
2288     MarkMenu("View.OpenChatWindow", ChatDlg);
2289     CursorAtEnd(&chatOptions[CHAT_IN]);
2290 }
2291
2292 void
2293 ChatProc ()
2294 {
2295     if(shellUp[ChatDlg]) PopDown(ChatDlg);
2296     else ChatPopUp();
2297 }
2298
2299 void
2300 ConsoleAutoPopUp (char *buf)
2301 {
2302         if(*buf == 27) { if(appData.icsActive && DialogExists(ChatDlg)) HardSetFocus (&chatOptions[CHAT_IN], ChatDlg); return; }
2303         if(!appData.autoBox) return;
2304         if(appData.icsActive) { // text typed to board in ICS mode: divert to ICS input box
2305             if(DialogExists(ChatDlg)) { // box already exists: append to current contents
2306                 char *p, newText[MSG_SIZ];
2307                 GetWidgetText(&chatOptions[CHAT_IN], &p);
2308                 snprintf(newText, MSG_SIZ, "%s%c", p, *buf);
2309                 SetWidgetText(&chatOptions[CHAT_IN], newText, ChatDlg);
2310                 if(shellUp[ChatDlg]) HardSetFocus (&chatOptions[CHAT_IN], ChatDlg); //why???
2311             } else { ASSIGN(line, buf); } // box did not exist: make sure it pops up with char in it
2312             ChatPopUp();
2313         } else PopUpMoveDialog(*buf);
2314 }
2315
2316 void
2317 EchoOn ()
2318 {
2319     if(!noEcho) return;
2320     system("stty echo");
2321     WidgetEcho(&chatOptions[CHAT_IN], 1);
2322     noEcho = False;
2323 }
2324
2325 void
2326 EchoOff ()
2327 {
2328     system("stty -echo");
2329     WidgetEcho(&chatOptions[CHAT_IN], 0);
2330     noEcho = True;
2331 }
2332
2333 //--------------------------------- Game-List options dialog ------------------------------------------
2334
2335 char *strings[LPUSERGLT_SIZE];
2336 int stringPtr;
2337
2338 void
2339 GLT_ClearList ()
2340 {
2341     strings[0] = NULL;
2342     stringPtr = 0;
2343 }
2344
2345 void
2346 GLT_AddToList (char *name)
2347 {
2348     strings[stringPtr++] = name;
2349     strings[stringPtr] = NULL;
2350 }
2351
2352 Boolean
2353 GLT_GetFromList (int index, char *name)
2354 {
2355   safeStrCpy(name, strings[index], MSG_SIZ);
2356   return TRUE;
2357 }
2358
2359 void
2360 GLT_DeSelectList ()
2361 {
2362 }
2363
2364 static void GLT_Button P((int n));
2365 static int GLT_OK P((int n));
2366
2367 static Option listOptions[] = {
2368 {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
2369 { 0,    0,     0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("factory") },
2370 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("up") },
2371 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("down") },
2372 { 0, SAME_ROW, 0, NULL, (void*) &GLT_OK, "", NULL, EndMark , "" }
2373 };
2374
2375 static int
2376 GLT_OK (int n)
2377 {
2378     GLT_ParseList();
2379     appData.gameListTags = strdup(lpUserGLT);
2380     GameListUpdate();
2381     return 1;
2382 }
2383
2384 static void
2385 GLT_Button (int n)
2386 {
2387     int index = SelectedListBoxItem (&listOptions[0]);
2388     char *p;
2389     if (index < 0) {
2390         DisplayError(_("No tag selected"), 0);
2391         return;
2392     }
2393     p = strings[index];
2394     if (n == 3) {
2395         if(index >= strlen(GLT_ALL_TAGS)) return;
2396         strings[index] = strings[index+1];
2397         strings[++index] = p;
2398         LoadListBox(&listOptions[0], "?", index, index-1); // only change the two specified entries
2399     } else
2400     if (n == 2) {
2401         if(index == 0) return;
2402         strings[index] = strings[index-1];
2403         strings[--index] = p;
2404         LoadListBox(&listOptions[0], "?", index, index+1);
2405     } else
2406     if (n == 1) {
2407       safeStrCpy(lpUserGLT, GLT_DEFAULT_TAGS, LPUSERGLT_SIZE);
2408       GLT_TagsToList(lpUserGLT);
2409       index = 0;
2410       LoadListBox(&listOptions[0], "?", -1, -1);
2411     }
2412     HighlightListBoxItem(&listOptions[0], index);
2413 }
2414
2415 void
2416 GameListOptionsPopUp (DialogClass parent)
2417 {
2418     safeStrCpy(lpUserGLT, appData.gameListTags, LPUSERGLT_SIZE);
2419     GLT_TagsToList(lpUserGLT);
2420
2421     GenericPopUp(listOptions, _("Game-list options"), TransientDlg, parent, MODAL, 0);
2422 }
2423
2424 void
2425 GameListOptionsProc ()
2426 {
2427     GameListOptionsPopUp(BoardWindow);
2428 }
2429
2430 //----------------------------- Error popup in various uses -----------------------------
2431
2432 /*
2433  * [HGM] Note:
2434  * XBoard has always had some pathologic behavior with multiple simultaneous error popups,
2435  * (which can occur even for modal popups when asynchrounous events, e.g. caused by engine, request a popup),
2436  * and this new implementation reproduces that as well:
2437  * Only the shell of the last instance is remembered in shells[ErrorDlg] (which replaces errorShell),
2438  * so that PopDowns ordered from the code always refer to that instance, and once that is down,
2439  * have no clue as to how to reach the others. For the Delete Window button calling PopDown this
2440  * has now been repaired, as the action routine assigned to it gets the shell passed as argument.
2441  */
2442
2443 int errorUp = False;
2444
2445 void
2446 ErrorPopDown ()
2447 {
2448     if (!errorUp) return;
2449     dialogError = errorUp = False;
2450     PopDown(ErrorDlg); PopDown(FatalDlg); // on explicit request we pop down any error dialog
2451     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
2452 }
2453
2454 int
2455 ErrorOK (int n)
2456 {
2457     dialogError = errorUp = False;
2458     PopDown(n == 1 ? FatalDlg : ErrorDlg); // kludge: non-modal dialogs have one less (dummy) option
2459     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
2460     return FALSE; // prevent second Popdown !
2461 }
2462
2463 static Option errorOptions[] = {
2464 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // dummy option: will never be displayed
2465 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // textValue field will be set before popup
2466 { 0,NO_CANCEL,0, NULL, (void*) &ErrorOK, "", NULL, EndMark , "" }
2467 };
2468
2469 void
2470 ErrorPopUp (char *title, char *label, int modal)
2471 {
2472     errorUp = True;
2473     errorOptions[1].name = label;
2474     if(dialogError = shellUp[TransientDlg])
2475         GenericPopUp(errorOptions+1, title, FatalDlg, TransientDlg, MODAL, 0); // pop up as daughter of the transient dialog
2476     else if(dialogError = shellUp[MasterDlg])
2477         GenericPopUp(errorOptions+1, title, FatalDlg, MasterDlg, MODAL, 0); // pop up as daughter of the master dialog
2478     else
2479         GenericPopUp(errorOptions+modal, title, modal ? FatalDlg: ErrorDlg, BoardWindow, modal, 0); // kludge: option start address indicates modality
2480 }
2481
2482 void
2483 DisplayError (String message, int error)
2484 {
2485     char buf[MSG_SIZ];
2486
2487     if (error == 0) {
2488         if (appData.debugMode || appData.matchMode) {
2489             fprintf(stderr, "%s: %s\n", programName, message);
2490         }
2491     } else {
2492         if (appData.debugMode || appData.matchMode) {
2493             fprintf(stderr, "%s: %s: %s\n",
2494                     programName, message, strerror(error));
2495         }
2496         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
2497         message = buf;
2498     }
2499     ErrorPopUp(_("Error"), message, FALSE);
2500 }
2501
2502
2503 void
2504 DisplayMoveError (String message)
2505 {
2506     fromX = fromY = -1;
2507     ClearHighlights();
2508     DrawPosition(TRUE, NULL); // selective redraw would miss the from-square of the rejected move, displayed empty after drag, but not marked damaged!
2509     if (appData.debugMode || appData.matchMode) {
2510         fprintf(stderr, "%s: %s\n", programName, message);
2511     }
2512     if (appData.popupMoveErrors) {
2513         ErrorPopUp(_("Error"), message, FALSE);
2514     } else {
2515         DisplayMessage(message, "");
2516     }
2517 }
2518
2519
2520 void
2521 DisplayFatalError (String message, int error, int status)
2522 {
2523     char buf[MSG_SIZ];
2524
2525     if(status == 666) { // ignore this error when ICS Console window is up
2526         if(shellUp[ChatDlg]) return;
2527         status = 0;
2528     }
2529
2530     errorExitStatus = status;
2531     if (error == 0) {
2532         fprintf(stderr, "%s: %s\n", programName, message);
2533     } else {
2534         fprintf(stderr, "%s: %s: %s\n",
2535                 programName, message, strerror(error));
2536         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
2537         message = buf;
2538     }
2539     if(mainOptions[W_BOARD].handle) {
2540         if (appData.popupExitMessage) {
2541             if(appData.icsActive) SendToICS("logout\n"); // [HGM] make sure no new games will be started
2542             ErrorPopUp(status ? _("Fatal Error") : _("Exiting"), message, TRUE);
2543         } else {
2544             ExitEvent(status);
2545         }
2546     }
2547 }
2548
2549 void
2550 DisplayInformation (String message)
2551 {
2552     ErrorPopDown();
2553     ErrorPopUp(_("Information"), message, TRUE);
2554 }
2555
2556 void
2557 DisplayNote (String message)
2558 {
2559     ErrorPopDown();
2560     ErrorPopUp(_("Note"), message, FALSE);
2561 }
2562
2563 void
2564 DisplayTitle (char *text)
2565 {
2566     char title[MSG_SIZ];
2567     char icon[MSG_SIZ];
2568
2569     if (text == NULL) text = "";
2570
2571     if(partnerUp) { SetDialogTitle(DummyDlg, text); return; }
2572
2573     if (*text != NULLCHAR) {
2574       safeStrCpy(icon, text, sizeof(icon)/sizeof(icon[0]) );
2575       safeStrCpy(title, text, sizeof(title)/sizeof(title[0]) );
2576     } else if (appData.icsActive) {
2577         snprintf(icon, sizeof(icon), "%s", appData.icsHost);
2578         snprintf(title, sizeof(title), "%s: %s", programName, appData.icsHost);
2579     } else if (appData.cmailGameName[0] != NULLCHAR) {
2580         snprintf(icon, sizeof(icon), "%s", "CMail");
2581         snprintf(title,sizeof(title), "%s: %s", programName, "CMail");
2582 #ifdef GOTHIC
2583     // [HGM] license: This stuff should really be done in back-end, but WinBoard already had a pop-up for it
2584     } else if (gameInfo.variant == VariantGothic) {
2585       safeStrCpy(icon,  programName, sizeof(icon)/sizeof(icon[0]) );
2586       safeStrCpy(title, GOTHIC,     sizeof(title)/sizeof(title[0]) );
2587 #endif
2588 #ifdef FALCON
2589     } else if (gameInfo.variant == VariantFalcon) {
2590       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
2591       safeStrCpy(title, FALCON, sizeof(title)/sizeof(title[0]) );
2592 #endif
2593     } else if (appData.noChessProgram) {
2594       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
2595       safeStrCpy(title, programName, sizeof(title)/sizeof(title[0]) );
2596     } else {
2597       safeStrCpy(icon, first.tidy, sizeof(icon)/sizeof(icon[0]) );
2598         snprintf(title,sizeof(title), "%s: %s", programName, first.tidy);
2599     }
2600     SetWindowTitle(text, title, icon);
2601 }
2602
2603 char *textPtr;
2604 char *texEscapes[] = { "s-1", "s0", "&", "*(L", "*(R", NULL };
2605
2606 int
2607 GetNext(FILE *f)
2608 {
2609     if(textPtr) return *textPtr ? *textPtr++ : EOF;
2610     return fgetc(f);
2611 }
2612
2613 static char *
2614 ReadLine (FILE *f)
2615 {
2616     static char buf[MSG_SIZ];
2617     int i = 0, c;
2618     while((c = GetNext(f)) != '\n') { if(c == EOF) return NULL; buf[i++] = c; }
2619     buf[i] = NULLCHAR;
2620     return buf;
2621 }
2622
2623 void
2624 GetHelpText (FILE *f, char *name)
2625 {
2626     char *line, buf[MSG_SIZ], title[MSG_SIZ], text[10000], *p = text, *q = text;
2627     int len, cnt = 0;
2628     while(*name == '\n') name++;
2629     snprintf(buf, MSG_SIZ, ".B %s", name);
2630     len = strlen(buf);
2631     for(len=3; buf[len] && buf[len] != '(' && buf[len] != ':' && buf[len] != '.' && buf[len] != '?' && buf[len] != '\n'; len++);
2632     buf[len] = NULLCHAR;
2633     while(buf[--len] == ' ') buf[len] = NULLCHAR; len++;
2634     snprintf(title, MSG_SIZ, "Help on '%s'", buf+3);
2635     while((line = ReadLine(f))) {
2636         if(!strncmp(line, buf, len) || !strncmp(line, ".SS ", 4) && !strncmp(line+4, buf+3, len-3)
2637                               || !strncmp(line, ".IX Item \"", 10) && !strncmp(line+10, buf+3, len-3)) {
2638             while((line = ReadLine(f)) && (cnt == 0 || strncmp(line, ".B ", 3) && strncmp(line, ".SS ", 4) && strncmp(line, ".IX ", 4))) {
2639                 if(!*line) { *p++ = '\n'; *p++ = '\n'; q = p; continue; }
2640                 if(*line == '.') continue;
2641                 *p++ = ' '; cnt++;
2642                 while(*line) {
2643                     if(*line < ' ') { line++; continue;}
2644                     if(*line == '\\') {
2645                         char **esc;
2646                         line++;
2647                         for(esc = texEscapes; *esc; esc++) {
2648                             len = strlen(*esc);
2649                             if(!strncmp(*esc, line, len)) {
2650                                 line += len;
2651                                 break;
2652                             }
2653                         }
2654                         continue;
2655                     }
2656                     if(*line == ' ' && p - q > 80) *line = '\n', q = p;
2657                     *p++ = *line++;
2658                 }
2659                 if(p - text > 9900) break;
2660             }
2661             *p = NULLCHAR;
2662             ErrorPopUp(title, text, FALSE);
2663             return;
2664         }
2665     }
2666     snprintf(text, MSG_SIZ, "No help available on '%s'\n", buf+3);
2667     DisplayNote(text);
2668 }
2669
2670 void
2671 DisplayHelp (char *name)
2672 {
2673     static char *xboardMan, *manText[2], tidy[MSG_SIZ], engMan[MSG_SIZ];
2674     char buf[MSG_SIZ], adapter[MSG_SIZ], *eng;
2675     int n = 0;
2676     FILE *f;
2677     if(!xboardMan) {
2678         xboardMan = BufferCommandOutput("man -w xboard", MSG_SIZ); // obtain path to XBoard's man file
2679         if(xboardMan) xboardMan[strlen(xboardMan)-1] = NULLCHAR;   // strip off traling linefeed
2680     }
2681     if(currentCps) { // for engine options we have to look in engine manual
2682         snprintf(buf, MSG_SIZ, "man -w ");            // get (tidied) engine name in buf
2683         TidyProgramName(currentCps->program, "localhost", adapter);       // name of binary we are actually running
2684         TidyProgramName(currentCps == &first ? appData.firstChessProgram : appData.secondChessProgram, "localhost", buf+7);
2685         if(strcmp(buf+7, adapter) && StrCaseStr(name, adapter) == name) { // option starts with name of apparent proxy for engine
2686             safeStrCpy(buf+7, adapter, MSG_SIZ-7);    // use adapter manual
2687             name += strlen(adapter);                  // strip adapter name of option
2688             while(*name == ' ') name++;
2689         }
2690         if(strcmp(buf, tidy)) {                       // is different engine from last time
2691             FREE(manText[1]); manText[1] = NULL;      // so any currently held text is worthless
2692             safeStrCpy(tidy, buf, MSG_SIZ);           // remember current engine
2693             eng = BufferCommandOutput(tidy, MSG_SIZ); // obtain path to  its man file
2694             if(*eng)
2695             safeStrCpy(engMan, eng, strlen(eng));     // and remember that too
2696             else *engMan = NULLCHAR;
2697             FREE(eng);
2698         }
2699         safeStrCpy(buf, engMan, MSG_SIZ); n = 1;      // use engine man
2700     } else snprintf(buf, MSG_SIZ, "%s", xboardMan);   // use xboard man
2701     f = fopen(buf, "r");
2702     if(f) {
2703         char *msg = "Right-clicking menu item or dialog text pops up help on it";
2704         ASSIGN(appData.suppress, msg);
2705         if(strstr(buf, ".gz")) { // man file is gzipped
2706             if(!manText[n]) {    // unzipped text not buffered yet
2707                 snprintf(tidy, MSG_SIZ, "gunzip -c %s", buf);
2708                 manText[n] = BufferCommandOutput(tidy, 250000); // store unzipped in buffer
2709             }
2710             textPtr = manText[n];// use buffered unzipped text
2711         } else textPtr = NULL;   // use plaintext man file directly
2712         GetHelpText(f, name);
2713         fclose(f);
2714     } else if(currentCps) DisplayNote("No manual is installed for this engine");
2715 }
2716
2717 #define PAUSE_BUTTON "P"
2718 #define PIECE_MENU_SIZE 18
2719 static String pieceMenuStrings[2][PIECE_MENU_SIZE+1] = {
2720     { N_("White"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
2721       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
2722       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
2723       N_("Empty square"), N_("Clear board"), NULL },
2724     { N_("Black"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
2725       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
2726       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
2727       N_("Empty square"), N_("Clear board"), NULL }
2728 };
2729 /* must be in same order as pieceMenuStrings! */
2730 static ChessSquare pieceMenuTranslation[2][PIECE_MENU_SIZE] = {
2731     { WhitePlay, (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2732         WhiteRook, WhiteQueen, WhiteKing, (ChessSquare) 0, WhiteAlfil,
2733         WhiteCannon, WhiteAngel, WhiteMarshall, (ChessSquare) 0,
2734         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2735     { BlackPlay, (ChessSquare) 0, BlackPawn, BlackKnight, BlackBishop,
2736         BlackRook, BlackQueen, BlackKing, (ChessSquare) 0, BlackAlfil,
2737         BlackCannon, BlackAngel, BlackMarshall, (ChessSquare) 0,
2738         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2739 };
2740
2741 #define DROP_MENU_SIZE 6
2742 static String dropMenuStrings[DROP_MENU_SIZE+1] = {
2743     "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"), N_("Queen"), NULL
2744   };
2745 /* must be in same order as dropMenuStrings! */
2746 static ChessSquare dropMenuTranslation[DROP_MENU_SIZE] = {
2747     (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2748     WhiteRook, WhiteQueen
2749 };
2750
2751 // [HGM] experimental code to pop up window just like the main window, using GenercicPopUp
2752
2753 static Option *Exp P((int n, int x, int y));
2754 void MenuCallback P((int n));
2755 void SizeKludge P((int n));
2756 static Option *LogoW P((int n, int x, int y));
2757 static Option *LogoB P((int n, int x, int y));
2758
2759 static int pmFromX = -1, pmFromY = -1;
2760 void *userLogo;
2761
2762 void
2763 DisplayLogos (Option *w1, Option *w2)
2764 {
2765         void *whiteLogo = first.programLogo, *blackLogo = second.programLogo;
2766         if(appData.autoLogo) {
2767           if(appData.noChessProgram) whiteLogo = blackLogo = NULL;
2768           if(appData.icsActive) whiteLogo = blackLogo = second.programLogo;
2769           switch(gameMode) { // pick logos based on game mode
2770             case IcsObserving:
2771                 whiteLogo = second.programLogo; // ICS logo
2772                 blackLogo = second.programLogo;
2773             default:
2774                 break;
2775             case IcsPlayingWhite:
2776                 if(!appData.zippyPlay) whiteLogo = userLogo;
2777                 blackLogo = second.programLogo; // ICS logo
2778                 break;
2779             case IcsPlayingBlack:
2780                 whiteLogo = second.programLogo; // ICS logo
2781                 blackLogo = appData.zippyPlay ? first.programLogo : userLogo;
2782                 break;
2783             case TwoMachinesPlay:
2784                 if(first.twoMachinesColor[0] == 'b') {
2785                     whiteLogo = second.programLogo;
2786                     blackLogo = first.programLogo;
2787                 }
2788                 break;
2789             case MachinePlaysWhite:
2790                 blackLogo = userLogo;
2791                 break;
2792             case MachinePlaysBlack:
2793                 whiteLogo = userLogo;
2794                 blackLogo = first.programLogo;
2795           }
2796         }
2797         DrawLogo(w1, whiteLogo);
2798         DrawLogo(w2, blackLogo);
2799 }
2800
2801 static void
2802 PMSelect (int n)
2803 {   // user callback for board context menus
2804     if (pmFromX < 0 || pmFromY < 0) return;
2805     if(n == W_DROP) DropMenuEvent(dropMenuTranslation[values[n]], pmFromX, pmFromY);
2806     else EditPositionMenuEvent(pieceMenuTranslation[n - W_MENUW][values[n]], pmFromX, pmFromY);
2807 }
2808
2809 static void
2810 CCB (int n)
2811 {
2812     shiftKey = (ShiftKeys() & 3) != 0;
2813     if(n < 0) { // button != 1
2814         n = -n;
2815         if(shiftKey && (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack)) {
2816             AdjustClock(n == W_BLACK, 1);
2817         }
2818     } else
2819     ClockClick(n == W_BLACK);
2820 }
2821
2822 Option mainOptions[] = { // description of main window in terms of generic dialog creator
2823 { 0, 0xCA, 0, NULL, NULL, "", NULL, BarBegin, "" }, // menu bar
2824   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_File") },
2825   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Edit") },
2826   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_View") },
2827   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Mode") },
2828   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Action") },
2829   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("E_ngine") },
2830   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Options") },
2831   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Help") },
2832 { 0, 0, 0, NULL, (void*)&SizeKludge, "", NULL, BarEnd, "" },
2833 { 0, LR|T2T|BORDER|SAME_ROW, 0, NULL, NULL, NULL, NULL, Label, "1" }, // optional title in window
2834 { 50,    LL|TT,            100, NULL, (void*) &LogoW, NULL, NULL, Skip, "" }, // white logo
2835 { 12,   L2L|T2T,           200, NULL, (void*) &CCB, NULL, NULL, Label, "White" }, // white clock
2836 { 13,   R2R|T2T|SAME_ROW,  200, NULL, (void*) &CCB, NULL, NULL, Label, "Black" }, // black clock
2837 { 50,    RR|TT|SAME_ROW,   100, NULL, (void*) &LogoB, NULL, NULL, Skip, "" }, // black logo
2838 { 0, LR|T2T|BORDER,        401, NULL, NULL, "", NULL, Skip, "2" }, // backup for title in window (if no room for other)
2839 { 0, LR|T2T|BORDER,        270, NULL, NULL, NULL, NULL, Label, "message", &appData.font }, // message field
2840 { 0, RR|TT|SAME_ROW,       125, NULL, NULL, "", NULL, BoxBegin, "" }, // (optional) button bar
2841   { 0,    0,     0, NULL, (void*) &ToStartEvent,  NULL, NULL, Button, N_("<<"), &appData.font },
2842   { 0, SAME_ROW, 0, NULL, (void*) &BackwardEvent, NULL, NULL, Button, N_("<"),  &appData.font },
2843   { 0, SAME_ROW, 0, NULL, (void*) &PauseEvent,    NULL, NULL, Button, N_(PAUSE_BUTTON), &appData.font },
2844   { 0, SAME_ROW, 0, NULL, (void*) &ForwardEvent,  NULL, NULL, Button, N_(">"),  &appData.font },
2845   { 0, SAME_ROW, 0, NULL, (void*) &ToEndEvent,    NULL, NULL, Button, N_(">>"), &appData.font },
2846 { 0, 0, 0, NULL, NULL, "", NULL, BoxEnd, "" },
2847 { 401, LR|TB, 401, NULL, (char*) &Exp, NULL, NULL, Graph, "shadow board" }, // board
2848   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[0], PopUp, "menuW" },
2849   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[1], PopUp, "menuB" },
2850   { -1, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, dropMenuStrings, PopUp, "menuD" },
2851 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2852 };
2853
2854 Option *
2855 LogoW (int n, int x, int y)
2856 {
2857     if(n == 10) DisplayLogos(&mainOptions[W_WHITE-1], NULL);
2858     return NULL;
2859 }
2860
2861 Option *
2862 LogoB (int n, int x, int y)
2863 {
2864     if(n == 10) DisplayLogos(NULL, &mainOptions[W_BLACK+1]);
2865     return NULL;
2866 }
2867
2868 void
2869 SizeKludge (int n)
2870 {   // callback called by GenericPopUp immediately after sizing the menu bar
2871     int width = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2872     int w = width - 44 - mainOptions[n].min;
2873     mainOptions[W_TITLE].max = w; // width left behind menu bar
2874     if(w < 0.4*width) // if no reasonable amount of space for title, force small layout
2875         mainOptions[W_SMALL].type = mainOptions[W_TITLE].type, mainOptions[W_TITLE].type = Skip;
2876 }
2877
2878 void
2879 MenuCallback (int n)
2880 {
2881     MenuProc *proc = (MenuProc *) (((MenuItem*)(mainOptions[n].choice))[values[n]].proc);
2882
2883     if(!proc) RecentEngineEvent(values[n] - firstEngineItem); else (proc)();
2884 }
2885
2886 static Option *
2887 Exp (int n, int x, int y)
2888 {
2889     static int but1, but3, oldW, oldH, oldX, oldY;
2890     int menuNr = -3, sizing, f, r;
2891     TimeMark now;
2892     extern Boolean right;
2893
2894     if(right) {  // kludgy way to let button 1 double as button 3 when back-end requests this
2895         if(but1 && n == 0) but1 = 0, but3 = 1;
2896         else if(n == -1) n = -3, right = FALSE;
2897     }
2898
2899     if(n == 0) { // motion
2900         oldX = x; oldY = y;
2901         if(SeekGraphClick(Press, x, y, 1)) return NULL;
2902         if((but1 || dragging == 2) && !PromoScroll(x, y)) DragPieceMove(x, y);
2903         if(but3) MovePV(x, y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
2904         if(appData.highlightDragging) {
2905             f = EventToSquare(x, BOARD_WIDTH);  if ( flipView && f >= 0) f = BOARD_WIDTH - 1 - f;
2906             r = EventToSquare(y, BOARD_HEIGHT); if (!flipView && r >= 0) r = BOARD_HEIGHT - 1 - r;
2907             HoverEvent(x, y, f, r);
2908         }
2909         return NULL;
2910     }
2911     if(n != 10 && PopDown(PromoDlg)) fromX = fromY = -1; // user starts fiddling with board when promotion dialog is up
2912     else GetTimeMark(&now);
2913     shiftKey = ShiftKeys();
2914     controlKey = (shiftKey & 0xC) != 0;
2915     shiftKey = (shiftKey & 3) != 0;
2916     switch(n) {
2917         case  1: LeftClick(Press,   x, y), but1 = 1; break;
2918         case -1: LeftClick(Release, x, y), but1 = 0; break;
2919         case  2: shiftKey = !shiftKey;
2920         case  3: menuNr = RightClick(Press,   x, y, &pmFromX, &pmFromY), but3 = 1; break;
2921         case -2: shiftKey = !shiftKey;
2922         case -3: menuNr = RightClick(Release, x, y, &pmFromX, &pmFromY), but3 = 0; break;
2923         case  4: Wheel(-1, oldX, oldY); break;
2924         case  5: Wheel(1, oldX, oldY); break;
2925         case 10:
2926             sizing = (oldW != x || oldH != y);
2927             oldW = x; oldH = y;
2928             InitDrawingHandle(mainOptions + W_BOARD);
2929             if(sizing && SubtractTimeMarks(&now, &programStartTime) > 10000) return NULL; // don't redraw while sizing (except at startup)
2930             DrawPosition(True, NULL);
2931         default:
2932             return NULL;
2933     }
2934
2935     switch(menuNr) {
2936       case 0: return &mainOptions[shiftKey ? W_MENUW: W_MENUB];
2937       case 1: SetupDropMenu(); return &mainOptions[W_DROP];
2938       case 2:
2939       case -1: ErrorPopDown();
2940       case -2:
2941       default: break; // -3, so no clicks caught
2942     }
2943     return NULL;
2944 }
2945
2946 Option *
2947 BoardPopUp (int squareSize, int lineGap, void *clockFontThingy)
2948 {
2949     int i, size = BOARD_WIDTH*(squareSize + lineGap) + lineGap, logo = appData.logoSize;
2950     int f = 2*appData.fixedSize; // width fudge, needed for unknown reasons to not clip board
2951     mainOptions[W_WHITE].choice = (char**) clockFontThingy;
2952     mainOptions[W_BLACK].choice = (char**) clockFontThingy;
2953     mainOptions[W_BOARD].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2954     mainOptions[W_BOARD].max = mainOptions[W_SMALL].max = size; // board size
2955     mainOptions[W_SMALL].max = size - 2; // board title (subtract border!)
2956     mainOptions[W_BLACK].max = mainOptions[W_WHITE].max = size/2-3; // clock width
2957     mainOptions[W_MESSG].max = appData.showButtonBar ? size-135+f : size-2+f; // message
2958     mainOptions[W_MENU].max = size-40; // menu bar
2959     mainOptions[W_TITLE].type = appData.titleInWindow ? Label : Skip ;
2960     if(logo && logo <= size/4) { // Activate logos
2961         mainOptions[W_WHITE-1].type = mainOptions[W_BLACK+1].type = Graph;
2962         mainOptions[W_WHITE-1].max  = mainOptions[W_BLACK+1].max  = logo;
2963         mainOptions[W_WHITE-1].value= mainOptions[W_BLACK+1].value= logo/2;
2964         mainOptions[W_WHITE].min  |= SAME_ROW;
2965         mainOptions[W_WHITE].max  = mainOptions[W_BLACK].max  -= logo + 4;
2966         mainOptions[W_WHITE].name = mainOptions[W_BLACK].name = "Double\nHeight";
2967     }
2968     if(!appData.showButtonBar) for(i=W_BUTTON; i<W_BOARD; i++) mainOptions[i].type = Skip;
2969     for(i=0; i<8; i++) mainOptions[i+1].choice = (char**) menuBar[i].mi;
2970     AppendEnginesToMenu(appData.recentEngineList);
2971     GenericPopUp(mainOptions, "XBoard", BoardWindow, BoardWindow, NONMODAL, 1); // allways top-level
2972     return mainOptions;
2973 }
2974
2975 static Option *
2976 SlaveExp (int n, int x, int y)
2977 {
2978     if(n == 10) { // expose event
2979         flipView = !flipView; partnerUp = !partnerUp;
2980         DrawPosition(True, NULL); // [HGM] dual: draw other board in other orientation
2981         flipView = !flipView; partnerUp = !partnerUp;
2982     }
2983     return NULL;
2984 }
2985
2986 Option dualOptions[] = { // auxiliary board window
2987 { 0, L2L|T2T,              198, NULL, NULL, NULL, NULL, Label, "White" }, // white clock
2988 { 0, R2R|T2T|SAME_ROW,     198, NULL, NULL, NULL, NULL, Label, "Black" }, // black clock
2989 { 0, LR|T2T|BORDER,        401, NULL, NULL, NULL, NULL, Label, "This feature is experimental" }, // message field
2990 { 401, LR|TT, 401, NULL, (char*) &SlaveExp, NULL, NULL, Graph, "shadow board" }, // board
2991 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2992 };
2993
2994 void
2995 SlavePopUp ()
2996 {
2997     int size = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2998     // copy params from main board
2999     dualOptions[0].choice = mainOptions[W_WHITE].choice;
3000     dualOptions[1].choice = mainOptions[W_BLACK].choice;
3001     dualOptions[3].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
3002     dualOptions[3].max = dualOptions[2].max = size; // board width
3003     dualOptions[0].max = dualOptions[1].max = size/2 - 3; // clock width
3004     GenericPopUp(dualOptions, "XBoard", DummyDlg, BoardWindow, NONMODAL, appData.topLevel);
3005     SlaveResize(dualOptions+3);
3006 }
3007
3008 void
3009 DisplayWhiteClock (long timeRemaining, int highlight)
3010 {
3011     if(appData.noGUI) return;
3012     if(twoBoards && partnerUp) {
3013         DisplayTimerLabel(&dualOptions[0], _("White"), timeRemaining, highlight);
3014         return;
3015     }
3016     DisplayTimerLabel(&mainOptions[W_WHITE], _("White"), timeRemaining, highlight);
3017     if(highlight) SetClockIcon(0);
3018 }
3019
3020 void
3021 DisplayBlackClock (long timeRemaining, int highlight)
3022 {
3023     if(appData.noGUI) return;
3024     if(twoBoards && partnerUp) {
3025         DisplayTimerLabel(&dualOptions[1], _("Black"), timeRemaining, highlight);
3026         return;
3027     }
3028     DisplayTimerLabel(&mainOptions[W_BLACK], _("Black"), timeRemaining, highlight);
3029     if(highlight) SetClockIcon(1);
3030 }
3031
3032
3033 //---------------------------------------------
3034
3035 void
3036 DisplayMessage (char *message, char *extMessage)
3037 {
3038   /* display a message in the message widget */
3039
3040   char buf[MSG_SIZ];
3041
3042   if (extMessage)
3043     {
3044       if (*message)
3045         {
3046           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
3047           message = buf;
3048         }
3049       else
3050         {
3051           message = extMessage;
3052         };
3053     };
3054
3055     safeStrCpy(lastMsg, message, MSG_SIZ); // [HGM] make available
3056
3057   /* need to test if messageWidget already exists, since this function
3058      can also be called during the startup, if for example a Xresource
3059      is not set up correctly */
3060   if(mainOptions[W_MESSG].handle)
3061     SetWidgetLabel(&mainOptions[W_MESSG], message);
3062
3063   return;
3064 }
3065
3066 //----------------------------------- File Browser -------------------------------
3067
3068 #ifdef HAVE_DIRENT_H
3069 #include <dirent.h>
3070 #else
3071 #include <sys/dir.h>
3072 #define dirent direct
3073 #endif
3074
3075 #include <sys/stat.h>
3076
3077 #define MAXFILES 1000
3078
3079 static DialogClass savDlg;
3080 static ChessProgramState *savCps;
3081 static FILE **savFP;
3082 static char *fileName, *extFilter, *savMode, **namePtr;
3083 static int folderPtr, filePtr, oldVal, byExtension, extFlag, pageStart, cnt;
3084 static char curDir[MSG_SIZ], title[MSG_SIZ], *folderList[MAXFILES], *fileList[MAXFILES];
3085
3086 static char *FileTypes[] = {
3087 "Chess Games",
3088 "Chess Positions",
3089 "Tournaments",
3090 "Opening Books",
3091 "Sound files",
3092 "Images",
3093 "Settings (*.ini)",
3094 "Log files",
3095 "All files",
3096 NULL,
3097 "PGN",
3098 "Old-Style Games",
3099 "FEN",
3100 "Old-Style Positions",
3101 NULL,
3102 NULL
3103 };
3104
3105 static char *Extensions[] = {
3106 ".pgn .game",
3107 ".fen .epd .pos",
3108 ".trn",
3109 ".bin",
3110 ".wav",
3111 ".png",
3112 ".ini",
3113 ".log",
3114 "",
3115 "INVALID",
3116 ".pgn",
3117 ".game",
3118 ".fen",
3119 ".pos",
3120 NULL,
3121 ""
3122 };
3123
3124 void DirSelProc P((int n, int sel));
3125 void FileSelProc P((int n, int sel));
3126 void SetTypeFilter P((int n));
3127 int BrowseOK P((int n));
3128 void Switch P((int n));
3129 void CreateDir P((int n));
3130
3131 Option browseOptions[] = {
3132 {   0,    LR|T2T,      500, NULL, NULL, NULL, NULL, Label, title },
3133 {   0,    L2L|T2T,     250, NULL, NULL, NULL, NULL, Label, N_("Directories:") },
3134 {   0,R2R|T2T|SAME_ROW,100, NULL, NULL, NULL, NULL, Label, N_("Files:") },
3135 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by name") },
3136 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by type") },
3137 { 300,    L2L|TB,      250, NULL, (void*) folderList, (char*) &DirSelProc, NULL, ListBox, "" },
3138 { 300, R2R|TB|SAME_ROW,250, NULL, (void*) fileList, (char*) &FileSelProc, NULL, ListBox, "" },
3139 {   0,       0,        300, NULL, (void*) &fileName, NULL, NULL, TextBox, N_("Filename:") },
3140 {   0,    SAME_ROW,    120, NULL, (void*) &CreateDir, NULL, NULL, Button, N_("New directory") },
3141 {   0, COMBO_CALLBACK, 150, NULL, (void*) &SetTypeFilter, NULL, FileTypes, ComboBox, N_("File type:") },
3142 {   0,    SAME_ROW,      0, NULL, (void*) &BrowseOK, "", NULL, EndMark , "" }
3143 };
3144
3145 int
3146 BrowseOK (int n)
3147 {
3148         if(!fileName[0]) { // it is enough to have a file selected
3149             if(browseOptions[6].textValue) { // kludge: if callback specified we browse for file
3150                 int sel = SelectedListBoxItem(&browseOptions[6]);
3151                 if(sel < 0 || sel >= filePtr) return FALSE;
3152                 ASSIGN(fileName, fileList[sel]);
3153             } else { // we browse for path
3154                 ASSIGN(fileName, curDir); // kludge: without callback we browse for path
3155             }
3156         }
3157         if(!fileName[0]) return FALSE; // refuse OK when no file
3158         if(!savMode[0]) { // browsing for name only (dialog Browse button)
3159                 if(fileName[0] == '/') // We already had a path name
3160                     snprintf(title, MSG_SIZ, "%s", fileName);
3161                 else
3162                     snprintf(title, MSG_SIZ, "%s/%s", curDir, fileName);
3163                 SetWidgetText((Option*) savFP, title, savDlg);
3164                 currentCps = savCps; // could return to Engine Settings dialog!
3165                 return TRUE;
3166         }
3167         *savFP = fopen(fileName, savMode);
3168         if(*savFP == NULL) return FALSE; // refuse OK if file not openable
3169         ASSIGN(*namePtr, fileName);
3170         ScheduleDelayedEvent(DelayedLoad, 50);
3171         currentCps = savCps; // not sure this is ever non-null
3172         return TRUE;
3173 }
3174
3175 int
3176 AlphaNumCompare (char *p, char *q)
3177 {
3178     while(*p) {
3179         if(isdigit(*p) && isdigit(*q) && atoi(p) != atoi(q))
3180              return (atoi(p) > atoi(q) ? 1 : -1);
3181         if(*p != *q) break;
3182         p++, q++;
3183     }
3184     if(*p == *q) return 0;
3185     return (*p > *q ? 1 : -1);
3186 }
3187
3188 int
3189 Comp (const void *s, const void *t)
3190 {
3191     char *p = *(char**) s, *q = *(char**) t;
3192     if(extFlag) {
3193         char *h; int r;
3194         while(h = strchr(p, '.')) p = h+1;
3195         if(p == *(char**) s) p = "";
3196         while(h = strchr(q, '.')) q = h+1;
3197         if(q == *(char**) t) q = "";
3198         r = AlphaNumCompare(p, q);
3199         if(r) return r;
3200     }
3201     return AlphaNumCompare( *(char**) s, *(char**) t );
3202 }
3203
3204 void
3205 ListDir (int pathFlag)
3206 {
3207         DIR *dir;
3208         struct dirent *dp;
3209         struct stat statBuf;
3210         static int lastFlag;
3211
3212         if(pathFlag < 0) pathFlag = lastFlag;
3213         lastFlag = pathFlag;
3214         dir = opendir(".");
3215         getcwd(curDir, MSG_SIZ);
3216         snprintf(title, MSG_SIZ, "%s   %s", _("Contents of"), curDir);
3217         folderPtr = filePtr = cnt = 0; // clear listing
3218
3219         while (dp = readdir(dir)) { // pass 1: list foders
3220             char *s = dp->d_name;
3221             if(!stat(s, &statBuf) && S_ISDIR(statBuf.st_mode)) { // stat succeeds and tells us it is directory
3222                 if(s[0] == '.' && strcmp(s, "..")) continue; // suppress hidden, except ".."
3223                 ASSIGN(folderList[folderPtr], s); if(folderPtr < MAXFILES-2) folderPtr++;
3224             } else if(!pathFlag) {
3225                 char *s = dp->d_name, match=0;
3226 //              if(cnt == pageStart) { ASSIGN }
3227                 if(s[0] == '.') continue; // suppress hidden files
3228                 if(extFilter[0]) { // [HGM] filter on extension
3229                     char *p = extFilter, *q;
3230                     do {
3231                         if(q = strchr(p, ' ')) *q = 0;
3232                         if(strstr(s, p)) match++;
3233                         if(q) *q = ' ';
3234                     } while(q && (p = q+1));
3235                     if(!match) continue;
3236                 }
3237                 if(filePtr == MAXFILES-2) continue;
3238                 if(cnt++ < pageStart) continue;
3239                 ASSIGN(fileList[filePtr], s); filePtr++;
3240             }
3241         }
3242         if(filePtr == MAXFILES-2) { ASSIGN(fileList[filePtr], _("  next page")); filePtr++; }
3243         FREE(folderList[folderPtr]); folderList[folderPtr] = NULL;
3244         FREE(fileList[filePtr]); fileList[filePtr] = NULL;
3245         closedir(dir);
3246         extFlag = 0;         qsort((void*)folderList, folderPtr, sizeof(char*), &Comp);
3247         extFlag = byExtension; qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
3248 }
3249
3250 void
3251 Refresh (int pathFlag)
3252 {
3253     ListDir(pathFlag); // and make new one
3254     LoadListBox(&browseOptions[5], "", -1, -1);
3255     LoadListBox(&browseOptions[6], "", -1, -1);
3256     SetWidgetLabel(&browseOptions[0], title);
3257 }
3258
3259 static char msg1[] = N_("FIRST TYPE DIRECTORY NAME HERE");
3260 static char msg2[] = N_("TRY ANOTHER NAME");
3261
3262 void
3263 CreateDir (int n)
3264 {
3265     char *name, *errmsg = "";
3266     GetWidgetText(&browseOptions[n-1], &name);
3267     if(!strcmp(name, msg1) || !strcmp(name, msg2)) return;
3268     if(!name[0]) errmsg = _(msg1); else
3269     if(mkdir(name, 0755)) errmsg = _(msg2);
3270     else {
3271         chdir(name);
3272         Refresh(-1);
3273     }
3274     SetWidgetText(&browseOptions[n-1], errmsg, BrowserDlg);
3275 }
3276
3277 void
3278 Switch (int n)
3279 {
3280     if(byExtension == (n == 4)) return;
3281     extFlag = byExtension = (n == 4);
3282     qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
3283     LoadListBox(&browseOptions[6], "", -1, -1);
3284 }
3285
3286 void
3287 SetTypeFilter (int n)
3288 {
3289     int j = values[n];
3290     if(j == browseOptions[n].value) return; // no change
3291     browseOptions[n].value = j;
3292     SetWidgetLabel(&browseOptions[n], FileTypes[j]);
3293     ASSIGN(extFilter, Extensions[j]);
3294     pageStart = 0;
3295     Refresh(-1); // uses pathflag remembered by ListDir
3296     values[n] = oldVal; // do not disturb combo settings of underlying dialog
3297 }
3298
3299 void
3300 FileSelProc (int n, int sel)
3301 {
3302     if(sel < 0 || fileList[sel] == NULL) return;
3303     if(sel == MAXFILES-2) { pageStart = cnt; Refresh(-1); return; }
3304     ASSIGN(fileName, fileList[sel]);
3305     if(BrowseOK(0)) PopDown(BrowserDlg);
3306 }
3307
3308 void
3309 DirSelProc (int n, int sel)
3310 {
3311     if(!chdir(folderList[sel])) { // cd succeeded, so we are in new directory now
3312         Refresh(-1);
3313     }
3314 }
3315
3316 void
3317 StartDir (char *filter, char *newName)
3318 {
3319     static char *gamesDir, *trnDir, *imgDir, *bookDir, *dirDir;
3320     static char curDir[MSG_SIZ];
3321     char **res = NULL;
3322     if(!filter || !*filter) return;
3323     if(strstr(filter, "dir")) {
3324         res = &dirDir;
3325         if(!dirDir) dirDir= strdup(dataDir);
3326     } else
3327     if(strstr(filter, "pgn")) res = &gamesDir; else
3328     if(strstr(filter, "bin")) res = &bookDir; else
3329     if(strstr(filter, "png")) res = &imgDir; else
3330     if(strstr(filter, "trn")) res = &trnDir; else
3331     if(strstr(filter, "fen")) res = &appData.positionDir;
3332     if(res) {
3333         if(newName) {
3334             char *p, *q;
3335             if(*newName) {
3336                 ASSIGN(*res, newName);
3337                 for(p=*res; q=strchr(p, '/');) p = q + 1; *p = NULLCHAR;
3338             }
3339         }
3340         if(*curDir) {
3341             chdir(curDir);
3342             *curDir = NULLCHAR;
3343         } else {
3344             getcwd(curDir, MSG_SIZ);
3345             if(*res && **res) chdir(*res);
3346         }
3347     }
3348 }
3349
3350 void
3351 Browse (DialogClass dlg, char *label, char *proposed, char *ext, Boolean pathFlag, char *mode, char **name, FILE **fp)
3352 {
3353     int j=0;
3354     savFP = fp; savMode = mode, namePtr = name, savCps = currentCps, oldVal = values[9], savDlg = dlg; // save params, for use in callback
3355     ASSIGN(extFilter, ext);
3356     ASSIGN(fileName, proposed ? proposed : "");
3357     for(j=0; Extensions[j]; j++) // look up actual value in list of possible values, to get selection nr
3358         if(extFilter && !strcmp(extFilter, Extensions[j])) break;
3359     if(Extensions[j] == NULL) { j++; ASSIGN(FileTypes[j], extFilter); }
3360     browseOptions[9].value = j;
3361     browseOptions[6].textValue = (char*) (pathFlag ? NULL : &FileSelProc); // disable file listbox during path browsing
3362     pageStart = 0; ListDir(pathFlag);
3363     currentCps = NULL;
3364     GenericPopUp(browseOptions, label, BrowserDlg, dlg, MODAL, 0);
3365     SetWidgetLabel(&browseOptions[9], FileTypes[j]);
3366 }
3367
3368 static char *openName;
3369 FileProc fileProc;
3370 char *fileOpenMode;
3371 FILE *openFP;
3372
3373 void
3374 DelayedLoad ()
3375 {
3376   (void) (*fileProc)(openFP, 0, openName);
3377 }
3378
3379 void
3380 FileNamePopUp (char *label, char *def, char *filter, FileProc proc, char *openMode)
3381 {
3382     fileProc = proc;            /* I can't see a way not */
3383     fileOpenMode = openMode;    /*   to use globals here */
3384     FileNamePopUpWrapper(label, def, filter, proc, False, openMode, &openName, &openFP);
3385 }
3386
3387 void
3388 ActivateTheme (int col)
3389 {
3390     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap; else lineGap = defaultLineGap;
3391     InitDrawingParams(strcmp(oldPieceDir, appData.pieceDirectory));
3392     InitDrawingSizes(-1, 0);
3393     DrawPosition(True, NULL);
3394 }
3395