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