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