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