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