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