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