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