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