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