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