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