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