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