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