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