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