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