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