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