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