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