Fix popdown of Error/Help dialog through window-close button
[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 + (msg == NULL), 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 static Boolean noEcho;
1314
1315 static void
1316 SaveInHistory (char *cmd)
1317 {
1318   if(noEcho) return; // do not save password!
1319   if (history[histIn] != NULL) {
1320     free(history[histIn]);
1321     history[histIn] = NULL;
1322   }
1323   if (*cmd == NULLCHAR) return;
1324   history[histIn] = StrSave(cmd);
1325   histIn = (histIn + 1) % HISTORY_SIZE;
1326   if (history[histIn] != NULL) {
1327     free(history[histIn]);
1328     history[histIn] = NULL;
1329   }
1330   histP = histIn;
1331 }
1332
1333 static char *
1334 PrevInHistory (char *cmd)
1335 {
1336   int newhp;
1337   if (histP == histIn) {
1338     if (history[histIn] != NULL) free(history[histIn]);
1339     history[histIn] = StrSave(cmd);
1340   }
1341   newhp = (histP - 1 + HISTORY_SIZE) % HISTORY_SIZE;
1342   if (newhp == histIn || history[newhp] == NULL) return NULL;
1343   histP = newhp;
1344   return history[histP];
1345 }
1346
1347 static char *
1348 NextInHistory ()
1349 {
1350   if (histP == histIn) return NULL;
1351   histP = (histP + 1) % HISTORY_SIZE;
1352   return history[histP];
1353 }
1354 // end of borrowed code
1355
1356 #define INPUT 0
1357
1358 Option boxOptions[] = {
1359 {  30, T_TOP, 400, NULL, (void*) &icsText, NULL, NULL, TextBox, "" },
1360 {  0,  NO_OK,   0, NULL, NULL, "", NULL, EndMark , "" }
1361 };
1362
1363 void
1364 ICSInputSendText ()
1365 {
1366     char *val;
1367
1368     GetWidgetText(&boxOptions[INPUT], &val);
1369     SaveInHistory(val);
1370     SendMultiLineToICS(val);
1371     SetWidgetText(&boxOptions[INPUT], "", InputBoxDlg);
1372 }
1373
1374 void
1375 IcsKey (int n)
1376 {   // [HGM] input: let up-arrow recall previous line from history
1377     char *val = NULL; // to suppress spurious warning
1378
1379     if (!shellUp[InputBoxDlg]) return;
1380     switch(n) {
1381       case 0:
1382         ICSInputSendText();
1383         return;
1384       case 1:
1385         GetWidgetText(&boxOptions[INPUT], &val);
1386         val = PrevInHistory(val);
1387         break;
1388       case -1:
1389         val = NextInHistory();
1390     }
1391     SetWidgetText(&boxOptions[INPUT], val = val ? val : "", InputBoxDlg);
1392     SetInsertPos(&boxOptions[INPUT], strlen(val));
1393 }
1394
1395 void
1396 ICSInputBoxPopUp ()
1397 {
1398     MarkMenu("View.ICSInputBox", InputBoxDlg);
1399     if(GenericPopUp(boxOptions, _("ICS input box"), InputBoxDlg, BoardWindow, NONMODAL, 0))
1400         AddHandler(&boxOptions[INPUT], InputBoxDlg, 3);
1401     CursorAtEnd(&boxOptions[INPUT]);
1402 }
1403
1404 void
1405 IcsInputBoxProc ()
1406 {
1407     if (!PopDown(InputBoxDlg)) ICSInputBoxPopUp();
1408 }
1409
1410 //--------------------------------------------- Move Type In ------------------------------------------
1411
1412 static int TypeInOK P((int n));
1413
1414 Option typeOptions[] = {
1415 { 30, T_TOP, 400, NULL, (void*) &icsText, NULL, NULL, TextBox, "" },
1416 { 0,  NO_OK,   0, NULL, (void*) &TypeInOK, "", NULL, EndMark , "" }
1417 };
1418
1419 static int
1420 TypeInOK (int n)
1421 {
1422     TypeInDoneEvent(icsText);
1423     return TRUE;
1424 }
1425
1426 void
1427 PopUpMoveDialog (char firstchar)
1428 {
1429     static char buf[2];
1430     buf[0] = firstchar; ASSIGN(icsText, buf);
1431     if(GenericPopUp(typeOptions, _("Type a move"), TransientDlg, BoardWindow, MODAL, 0))
1432         AddHandler(&typeOptions[0], TransientDlg, 2);
1433     CursorAtEnd(&typeOptions[0]);
1434 }
1435
1436 void
1437 BoxAutoPopUp (char *buf)
1438 {       // only used in Xaw. GTK calls ConsoleAutoPopUp in stead (when we type to board)
1439         if(!appData.autoBox) return;
1440         if(appData.icsActive) { // text typed to board in ICS mode: divert to ICS input box
1441             if(DialogExists(InputBoxDlg)) { // box already exists: append to current contents
1442                 char *p, newText[MSG_SIZ];
1443                 GetWidgetText(&boxOptions[INPUT], &p);
1444                 snprintf(newText, MSG_SIZ, "%s%c", p, *buf);
1445                 SetWidgetText(&boxOptions[INPUT], newText, InputBoxDlg);
1446                 if(shellUp[InputBoxDlg]) HardSetFocus (&boxOptions[INPUT], InputBoxDlg); //why???
1447             } else icsText = buf; // box did not exist: make sure it pops up with char in it
1448             ICSInputBoxPopUp();
1449         } else PopUpMoveDialog(*buf);
1450 }
1451
1452 //------------------------------------------ Engine Settings ------------------------------------
1453
1454 void
1455 SettingsPopUp (ChessProgramState *cps)
1456 {
1457    if(!cps->nrOptions) { DisplayNote(_("Engine has no options")); return; }
1458    currentCps = cps;
1459    GenericPopUp(cps->option, _("Engine Settings"), TransientDlg, BoardWindow, MODAL, 0);
1460 }
1461
1462 void
1463 FirstSettingsProc ()
1464 {
1465     SettingsPopUp(&first);
1466 }
1467
1468 void
1469 SecondSettingsProc ()
1470 {
1471    if(WaitForEngine(&second, SettingsMenuIfReady)) return;
1472    SettingsPopUp(&second);
1473 }
1474
1475 void
1476 RefreshSettingsDialog (ChessProgramState *cps, int val)
1477 {
1478    if(val == 1) { // option values changed
1479       if(shellUp[TransientDlg] && cps == currentCps) {
1480          GenericUpdate(cps->option, -1); // normally update values when dialog is up
1481       }
1482       return; // and be done
1483    }
1484    if(val == 2) { // option list changed
1485       if(!shellUp[TransientDlg] || cps != currentCps) return; // our dialog is not up, so nothing to do
1486    }
1487    PopDown(TransientDlg); // make sure any other dialog closes first
1488    SettingsPopUp(cps);    // and popup new one
1489 }
1490
1491 //----------------------------------------------- Load Engine --------------------------------------
1492
1493 char *engineDir, *engineLine, *nickName, *params;
1494 Boolean isUCI, isUSI, hasBook, storeVariant, v1, addToList, useNick, secondEng;
1495
1496 static void EngSel P((int n, int sel));
1497 static int InstallOK P((int n));
1498
1499 static Option installOptions[] = {
1500 {   0,LR|T2T, 0, NULL, NULL, NULL, NULL, Label, N_("Select engine from list:") },
1501 { 300,LR|TB,200, NULL, (void*) engineMnemonic, (char*) &EngSel, NULL, ListBox, "" },
1502 { 0,SAME_ROW, 0, NULL, NULL, NULL, NULL, Break, NULL },
1503 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("or specify one below:") },
1504 {   0,  0,    0, NULL, (void*) &nickName, NULL, NULL, TextBox, N_("Nickname (optional):") },
1505 {   0,  0,    0, NULL, (void*) &useNick, NULL, NULL, CheckBox, N_("Use nickname in PGN player tags of engine-engine games") },
1506 {   0,  0,    0, NULL, (void*) &engineDir, NULL, NULL, PathName, N_("Engine Directory:") },
1507 {   0,  0,    0, NULL, (void*) &engineName, NULL, NULL, FileName, N_("Engine Command:") },
1508 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("(Directory will be derived from engine path when empty)") },
1509 {   0,  0,    0, NULL, (void*) &isUCI, NULL, NULL, CheckBox, N_("UCI") },
1510 {   0,  0,    0, NULL, (void*) &isUSI, NULL, NULL, CheckBox, N_("USI/UCCI (uses specified -uxiAdapter)") },
1511 {   0,  0,    0, NULL, (void*) &v1, NULL, NULL, CheckBox, N_("WB protocol v1 (do not wait for engine features)") },
1512 {   0,  0,    0, NULL, (void*) &hasBook, NULL, NULL, CheckBox, N_("Must not use GUI book") },
1513 {   0,  0,    0, NULL, (void*) &addToList, NULL, NULL, CheckBox, N_("Add this engine to the list") },
1514 {   0,  0,    0, NULL, (void*) &storeVariant, NULL, NULL, CheckBox, N_("Force current variant with this engine") },
1515 {   0,  0,    0, NULL, (void*) &InstallOK, "", NULL, EndMark , "" }
1516 };
1517
1518 static int
1519 InstallOK (int n)
1520 {
1521     if(n && (n = SelectedListBoxItem(&installOptions[1])) > 0) { // called by pressing OK, and engine selected
1522         ASSIGN(engineLine, engineList[n]);
1523     }
1524     PopDown(TransientDlg); // early popdown, to allow FreezeUI to instate grab
1525     if(isUSI) {
1526         isUCI = 2; // kludge to pass isUSI to Load()
1527         if(!*appData.ucciAdapter) { ASSIGN(appData.ucciAdapter, "usi2wb -%variant \"%fcp\"\"%fd\""); } // make sure -uxiAdapter is defined
1528     }
1529     if(!secondEng) Load(&first, 0); else Load(&second, 1);
1530     return FALSE; // no double PopDown!
1531 }
1532
1533 static void
1534 EngSel (int n, int sel)
1535 {
1536     int nr;
1537     char buf[MSG_SIZ];
1538     if(sel < 1) buf[0] = NULLCHAR; // back to top level
1539     else if(engineList[sel][0] == '#') safeStrCpy(buf, engineList[sel], MSG_SIZ); // group header, open group
1540     else { // normal line, select engine
1541         ASSIGN(engineLine, engineList[sel]);
1542         InstallOK(0);
1543         return;
1544     }
1545     nr = NamesToList(firstChessProgramNames, engineList, engineMnemonic, buf); // replace list by only the group contents
1546     ASSIGN(engineMnemonic[0], buf);
1547     LoadListBox(&installOptions[1], _("# no engines are installed"), -1, -1);
1548     HighlightWithScroll(&installOptions[1], 0, nr);
1549 }
1550
1551 static void
1552 LoadEngineProc (int engineNr, char *title)
1553 {
1554    isUCI = isUSI = storeVariant = v1 = useNick = False; addToList = hasBook = True; // defaults
1555    secondEng = engineNr;
1556    if(engineLine)   free(engineLine);   engineLine = strdup("");
1557    if(engineDir)    free(engineDir);    engineDir = strdup(".");
1558    if(nickName)     free(nickName);     nickName = strdup("");
1559    if(params)       free(params);       params = strdup("");
1560    ASSIGN(engineMnemonic[0], "");
1561    NamesToList(firstChessProgramNames, engineList, engineMnemonic, "");
1562    GenericPopUp(installOptions, title, TransientDlg, BoardWindow, MODAL, 0);
1563 }
1564
1565 void
1566 LoadEngine1Proc ()
1567 {
1568     LoadEngineProc (0, _("Load first engine"));
1569 }
1570
1571 void
1572 LoadEngine2Proc ()
1573 {
1574     LoadEngineProc (1, _("Load second engine"));
1575 }
1576
1577 //----------------------------------------------------- Edit Book -----------------------------------------
1578
1579 void
1580 EditBookProc ()
1581 {
1582     EditBookEvent();
1583 }
1584
1585 //--------------------------------------------------- New Shuffle Game ------------------------------
1586
1587 static void SetRandom P((int n));
1588
1589 static int
1590 ShuffleOK (int n)
1591 {
1592     ResetGameEvent();
1593     return 1;
1594 }
1595
1596 static Option shuffleOptions[] = {
1597   {   0,  0,    0, NULL, (void*) &shuffleOpenings, NULL, NULL, CheckBox, N_("shuffle") },
1598   {   0,  0,    0, NULL, (void*) &appData.fischerCastling, NULL, NULL, CheckBox, N_("Fischer castling") },
1599   { 0,-1,2000000000, NULL, (void*) &appData.defaultFrcPosition, "", NULL, Spin, N_("Start-position number:") },
1600   {   0,  0,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("randomize") },
1601   {   0,  SAME_ROW,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("pick fixed") },
1602   { 0,SAME_ROW, 0, NULL, (void*) &ShuffleOK, "", NULL, EndMark , "" }
1603 };
1604
1605 static void
1606 SetRandom (int n)
1607 {
1608     int r = n==2 ? -1 : random() & (1<<30)-1;
1609     char buf[MSG_SIZ];
1610     snprintf(buf, MSG_SIZ,  "%d", r);
1611     SetWidgetText(&shuffleOptions[1], buf, TransientDlg);
1612     SetWidgetState(&shuffleOptions[0], True);
1613 }
1614
1615 void
1616 ShuffleMenuProc ()
1617 {
1618     GenericPopUp(shuffleOptions, _("New Shuffle Game"), TransientDlg, BoardWindow, MODAL, 0);
1619 }
1620
1621 //------------------------------------------------------ Time Control -----------------------------------
1622
1623 static int TcOK P((int n));
1624 int tmpMoves, tmpTc, tmpInc, tmpOdds1, tmpOdds2, tcType, by60;
1625
1626 static void SetTcType P((int n));
1627
1628 static char *
1629 Value (int n)
1630 {
1631         static char buf[MSG_SIZ];
1632         snprintf(buf, MSG_SIZ, "%d", n);
1633         return buf;
1634 }
1635
1636 static Option tcOptions[] = {
1637 {   0,  0,    0, NULL, (void*) &SetTcType, NULL, NULL, Button, N_("classical") },
1638 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("incremental") },
1639 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("fixed max") },
1640 {   0,  0,    0, NULL, (void*) &by60,     "",  NULL, CheckBox, N_("Divide entered times by 60") },
1641 {   0,  0,  200, NULL, (void*) &tmpMoves, NULL, NULL, Spin, N_("Moves per session:") },
1642 {   0,  0,10000, NULL, (void*) &tmpTc,    NULL, NULL, Spin, N_("Initial time (min):") },
1643 {   0, 0, 10000, NULL, (void*) &tmpInc,   NULL, NULL, Spin, N_("Increment or max (sec/move):") },
1644 {   0,  0,    0, NULL, NULL, NULL, NULL, Label, N_("Time-Odds factors:") },
1645 {   0,  1, 1000, NULL, (void*) &tmpOdds1, NULL, NULL, Spin, N_("Engine #1") },
1646 {   0,  1, 1000, NULL, (void*) &tmpOdds2, NULL, NULL, Spin, N_("Engine #2 / Human") },
1647 {   0,  0,    0, NULL, (void*) &TcOK, "", NULL, EndMark , "" }
1648 };
1649
1650 static int
1651 TcOK (int n)
1652 {
1653     char *tc, buf[MSG_SIZ];
1654     if(tcType == 0 && tmpMoves <= 0) return 0;
1655     if(tcType == 2 && tmpInc <= 0) return 0;
1656     GetWidgetText(&tcOptions[5], &tc); // get original text, in case it is min:sec
1657     if(by60) snprintf(buf, MSG_SIZ, "%d:%02d", tmpTc/60, tmpTc%60), tc=buf;
1658     searchTime = 0;
1659     switch(tcType) {
1660       case 0:
1661         if(!ParseTimeControl(tc, -1, tmpMoves)) return 0;
1662         appData.movesPerSession = tmpMoves;
1663         ASSIGN(appData.timeControl, tc);
1664         appData.timeIncrement = -1;
1665         break;
1666       case 1:
1667         if(!ParseTimeControl(tc, tmpInc, 0)) return 0;
1668         ASSIGN(appData.timeControl, tc);
1669         appData.timeIncrement = (by60 ? tmpInc/60. : tmpInc);
1670         break;
1671       case 2:
1672         searchTime = (by60 ? tmpInc/60 : tmpInc);
1673     }
1674     appData.firstTimeOdds = first.timeOdds = tmpOdds1;
1675     appData.secondTimeOdds = second.timeOdds = tmpOdds2;
1676     Reset(True, True);
1677     return 1;
1678 }
1679
1680 static void
1681 SetTcType (int n)
1682 {
1683     switch(tcType = n) {
1684       case 0:
1685         SetWidgetText(&tcOptions[4], Value(tmpMoves), TransientDlg);
1686         SetWidgetText(&tcOptions[5], Value(tmpTc), TransientDlg);
1687         SetWidgetText(&tcOptions[6], _("Unused"), TransientDlg);
1688         break;
1689       case 1:
1690         SetWidgetText(&tcOptions[4], _("Unused"), TransientDlg);
1691         SetWidgetText(&tcOptions[5], Value(tmpTc), TransientDlg);
1692         SetWidgetText(&tcOptions[6], Value(tmpInc), TransientDlg);
1693         break;
1694       case 2:
1695         SetWidgetText(&tcOptions[4], _("Unused"), TransientDlg);
1696         SetWidgetText(&tcOptions[5], _("Unused"), TransientDlg);
1697         SetWidgetText(&tcOptions[6], Value(tmpInc), TransientDlg);
1698     }
1699 }
1700
1701 void
1702 TimeControlProc ()
1703 {
1704    if(gameMode != BeginningOfGame) {
1705         DisplayError(_("Changing time control during a game is not implemented"), 0);
1706         return;
1707    }
1708    tmpMoves = appData.movesPerSession;
1709    tmpInc = appData.timeIncrement; if(tmpInc < 0) tmpInc = 0;
1710    tmpOdds1 = tmpOdds2 = 1; tcType = 0;
1711    tmpTc = atoi(appData.timeControl);
1712    by60 = 0;
1713    GenericPopUp(tcOptions, _("Time Control"), TransientDlg, BoardWindow, MODAL, 0);
1714    SetTcType(searchTime ? 2 : appData.timeIncrement < 0 ? 0 : 1);
1715 }
1716
1717 //------------------------------- Ask Question -----------------------------------------
1718
1719 int SendReply P((int n));
1720 char pendingReplyPrefix[MSG_SIZ];
1721 ProcRef pendingReplyPR;
1722 char *answer;
1723
1724 Option askOptions[] = {
1725 { 0, 0, 0, NULL, NULL, NULL, NULL, Label,  NULL },
1726 { 0, 0, 0, NULL, (void*) &answer, "", NULL, TextBox, "" },
1727 { 0, 0, 0, NULL, (void*) &SendReply, "", NULL, EndMark , "" }
1728 };
1729
1730 int
1731 SendReply (int n)
1732 {
1733     char buf[MSG_SIZ];
1734     int err;
1735     char *reply=answer;
1736 //    GetWidgetText(&askOptions[1], &reply);
1737     safeStrCpy(buf, pendingReplyPrefix, sizeof(buf)/sizeof(buf[0]) );
1738     if (*buf) strncat(buf, " ", MSG_SIZ - strlen(buf) - 1);
1739     strncat(buf, reply, MSG_SIZ - strlen(buf) - 1);
1740     strncat(buf, "\n",  MSG_SIZ - strlen(buf) - 1);
1741     OutputToProcess(pendingReplyPR, buf, strlen(buf), &err); // does not go into debug file??? => bug
1742     if (err) DisplayFatalError(_("Error writing to chess program"), err, 0);
1743     return TRUE;
1744 }
1745
1746 void
1747 AskQuestion (char *title, char *question, char *replyPrefix, ProcRef pr)
1748 {
1749     safeStrCpy(pendingReplyPrefix, replyPrefix, sizeof(pendingReplyPrefix)/sizeof(pendingReplyPrefix[0]) );
1750     pendingReplyPR = pr;
1751     ASSIGN(answer, "");
1752     askOptions[0].name = question;
1753     if(GenericPopUp(askOptions, title, AskDlg, BoardWindow, MODAL, 0))
1754         AddHandler(&askOptions[1], AskDlg, 2);
1755 }
1756
1757 //---------------------------- Promotion Popup --------------------------------------
1758
1759 static int count;
1760
1761 static void PromoPick P((int n));
1762
1763 static Option promoOptions[] = {
1764 {   0,         0,    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,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1771 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1772 {   0, SAME_ROW | NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
1773 };
1774
1775 static void
1776 PromoPick (int n)
1777 {
1778     int promoChar = promoOptions[n+count].value;
1779
1780     PopDown(PromoDlg);
1781
1782     if (promoChar == 0) fromX = -1;
1783     if (fromX == -1) return;
1784
1785     if (! promoChar) {
1786         fromX = fromY = -1;
1787         ClearHighlights();
1788         return;
1789     }
1790     if(promoChar == '=' && !IS_SHOGI(gameInfo.variant)) promoChar = NULLCHAR;
1791     UserMoveEvent(fromX, fromY, toX, toY, promoChar);
1792
1793     if (!appData.highlightLastMove || gotPremove) ClearHighlights();
1794     if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
1795     fromX = fromY = -1;
1796 }
1797
1798 static void
1799 SetPromo (char *name, int nr, char promoChar)
1800 {
1801     ASSIGN(promoOptions[nr].name, name);
1802     promoOptions[nr].value = promoChar;
1803     promoOptions[nr].min = SAME_ROW;
1804 }
1805
1806 void
1807 PromotionPopUp (char choice)
1808 { // choice depends on variant: prepare dialog acordingly
1809   count = 8;
1810   SetPromo(_("Cancel"), --count, -1); // Beware: GenericPopUp cannot handle user buttons named "cancel" (lowe case)!
1811   if(choice != '+' && !IS_SHOGI(gameInfo.variant)) {
1812     if (!appData.testLegality || gameInfo.variant == VariantSuicide ||
1813         gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove) ||
1814         gameInfo.variant == VariantGiveaway) {
1815       SetPromo(_("King"), --count, 'k');
1816     }
1817     if(gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove)) {
1818       SetPromo(_("Captain"), --count, 'c');
1819       SetPromo(_("Lieutenant"), --count, 'l');
1820       SetPromo(_("General"), --count, 'g');
1821       SetPromo(_("Warlord"), --count, 'w');
1822     } else {
1823       SetPromo(_("Knight"), --count, 'n');
1824       SetPromo(_("Bishop"), --count, 'b');
1825       SetPromo(_("Rook"), --count, 'r');
1826       if(gameInfo.variant == VariantCapablanca ||
1827          gameInfo.variant == VariantGothic ||
1828          gameInfo.variant == VariantCapaRandom) {
1829         SetPromo(_("Archbishop"), --count, 'a');
1830         SetPromo(_("Chancellor"), --count, 'c');
1831       }
1832       SetPromo(_("Queen"), --count, 'q');
1833       if(gameInfo.variant == VariantChuChess)
1834         SetPromo(_("Lion"), --count, 'l');
1835     }
1836   } else // [HGM] shogi
1837   {
1838       SetPromo(_("Defer"), --count, '=');
1839       SetPromo(_("Promote"), --count, '+');
1840   }
1841   promoOptions[count].min = 0;
1842   GenericPopUp(promoOptions + count, "Promotion", PromoDlg, BoardWindow, NONMODAL, 0);
1843 }
1844
1845 //---------------------------- Chat Windows ----------------------------------------------
1846
1847 static char *line, *memo, *chatMemo, *partner, *texts[MAX_CHAT], dirty[MAX_CHAT], *inputs[MAX_CHAT], *icsLine, *tmpLine;
1848 static int activePartner;
1849 int hidden = 1;
1850
1851 void ChatSwitch P((int n));
1852 int  ChatOK P((int n));
1853
1854 #define CHAT_ICS     6
1855 #define CHAT_PARTNER 8
1856 #define CHAT_OUT    11
1857 #define CHAT_PANE   12
1858 #define CHAT_IN     13
1859
1860 void PaneSwitch P((void));
1861 void ClearChat P((void));
1862
1863 WindowPlacement wpTextMenu;
1864
1865 int
1866 ContextMenu (Option *opt, int button, int x, int y, char *text, int index)
1867 { // callback for ICS-output clicks; handles button 3, passes on other events
1868   int h;
1869   if(button == -3) return TRUE; // supress default GTK context menu on up-click
1870   if(button != 3) return FALSE;
1871   if(index == -1) { // pre-existing selection in memo
1872     strncpy(clickedWord, text, MSG_SIZ);
1873   } else { // figure out what word was clicked
1874     char *start, *end;
1875     start = end = text + index;
1876     while(isalnum(*end)) end++;
1877     while(start > text && isalnum(start[-1])) start--;
1878     clickedWord[0] = NULLCHAR;
1879     if(end-start >= 80) end = start + 80; // intended for small words and numbers
1880     strncpy(clickedWord, start, end-start); clickedWord[end-start] = NULLCHAR;
1881   }
1882   click = !shellUp[TextMenuDlg]; // request auto-popdown of textmenu when we popped it up
1883   h = wpTextMenu.height; // remembered height of text menu
1884   if(h <= 0) h = 65;     // when not available, position w.r.t. top
1885   GetPlacement(ChatDlg, &wpTextMenu);
1886   if(opt->target == (void*) &chatMemo) wpTextMenu.y += (wpTextMenu.height - 30)/2; // click in chat
1887   wpTextMenu.x += x - 50; wpTextMenu.y += y - h + 50; // request positioning
1888   if(wpTextMenu.x < 0) wpTextMenu.x = 0;
1889   if(wpTextMenu.y < 0) wpTextMenu.y = 0;
1890   wpTextMenu.width = wpTextMenu.height = -1;
1891   IcsTextPopUp();
1892   return TRUE;
1893 }
1894
1895 Option chatOptions[] = {
1896 {  0,  0,   0, NULL, NULL, NULL, NULL, Label , N_("Chats:") },
1897 { 1, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
1898 { 2, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
1899 { 3, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
1900 { 4, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
1901 { 5, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
1902 { 250, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &memo, NULL, (void*) &ContextMenu, TextBox, "" },
1903 {  0,  0,   0, NULL, NULL, "", NULL, Break , "" },
1904 { 0,   T_TOP,    100, NULL, (void*) &partner, NULL, NULL, TextBox, N_("Chat partner:") },
1905 {  0, SAME_ROW, 0, NULL, (void*) &ClearChat,  NULL, NULL, Button, N_("End Chat") },
1906 {  0, SAME_ROW, 0, NULL, (void*) &PaneSwitch, NULL, NULL, Button, N_("Hide") },
1907 { 250, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &chatMemo, NULL, (void*) &ContextMenu, TextBox, "" },
1908 {  0,  0,   0, NULL, NULL, "", NULL, Break , "" },
1909 {  0,    0,  510, NULL, (void*) &line, NULL, NULL, TextBox, "" },
1910 { 0, NO_OK|SAME_ROW, 0, NULL, (void*) &ChatOK, NULL, NULL, EndMark , "" }
1911 };
1912
1913 static void
1914 PutText (char *text, int pos)
1915 {
1916     char buf[MSG_SIZ], *p;
1917     DialogClass dlg = ChatDlg;
1918     Option *opt = &chatOptions[CHAT_IN];
1919
1920     if(strstr(text, "$add ") == text) {
1921         GetWidgetText(&boxOptions[INPUT], &p);
1922         snprintf(buf, MSG_SIZ, "%s%s", p, text+5); text = buf;
1923         pos += strlen(p) - 5;
1924     }
1925     if(shellUp[InputBoxDlg]) opt = &boxOptions[INPUT], dlg = InputBoxDlg; // for the benefit of Xaw give priority to ICS Input Box
1926     SetWidgetText(opt, text, dlg);
1927     SetInsertPos(opt, pos);
1928     HardSetFocus(opt, dlg);
1929     CursorAtEnd(opt);
1930 }
1931
1932 int
1933 IcsHist (int n, Option *opt, DialogClass dlg)
1934 {   // [HGM] input: let up-arrow recall previous line from history
1935     char *val = NULL; // to suppress spurious warning
1936     int chat, start;
1937
1938     if(opt != &chatOptions[CHAT_IN] && !(opt == &chatOptions[CHAT_PARTNER] && n == 33)) return 0;
1939     switch(n) {
1940       case 5:
1941         if(!hidden) ClearChat();
1942         break;
1943       case 8:
1944         if(!hidden) PaneSwitch();
1945         break;
1946       case 33: // <Esc>
1947         if(1) BoardToTop(); else
1948         if(hidden) BoardToTop();
1949         else PaneSwitch();
1950         break;
1951       case 15:
1952         NewChat(lastTalker);
1953         break;
1954       case 14:
1955         for(chat=0; chat < MAX_CHAT; chat++) if(!chatPartner[chat][0]) break;
1956         if(chat < MAX_CHAT) ChatSwitch(chat + 1);
1957         break;
1958       case 10: // <Tab>
1959         chat = start = (activePartner - hidden + MAX_CHAT) % MAX_CHAT;
1960         while(!dirty[chat = (chat + 1)%MAX_CHAT]) if(chat == start) break;
1961         if(!dirty[chat])
1962         while(!chatPartner[chat = (chat + 1)%MAX_CHAT][0]) if(chat == start) break;
1963         if(!chatPartner[chat][0]) break; // if all unused, ignore
1964         ChatSwitch(chat + 1);
1965         break;
1966       case 1:
1967         GetWidgetText(opt, &val);
1968         val = PrevInHistory(val);
1969         break;
1970       case -1:
1971         val = NextInHistory();
1972     }
1973     SetWidgetText(opt, val = val ? val : "", dlg);
1974     SetInsertPos(opt, strlen(val));
1975     return 1;
1976 }
1977
1978 void
1979 OutputChatMessage (int partner, char *mess)
1980 {
1981     char *p = texts[partner];
1982     int len = strlen(mess) + 1;
1983
1984     if(!DialogExists(ChatDlg)) return;
1985     if(p) len += strlen(p);
1986     texts[partner] = (char*) malloc(len);
1987     snprintf(texts[partner], len, "%s%s", p ? p : "", mess);
1988     FREE(p);
1989     if(partner == activePartner && !hidden) {
1990         AppendText(&chatOptions[CHAT_OUT], mess);
1991         SetInsertPos(&chatOptions[CHAT_OUT], len-2);
1992     } else {
1993         SetColor("#FFC000", &chatOptions[partner + 1]);
1994         dirty[partner] = 1;
1995     }
1996 }
1997
1998 int
1999 ChatOK (int n)
2000 {   // can only be called through <Enter> in chat-partner text-edit, as there is no OK button
2001     char buf[MSG_SIZ];
2002
2003     if(!hidden && (!partner || strcmp(partner, chatPartner[activePartner]) || !*partner)) {
2004         safeStrCpy(chatPartner[activePartner], partner, MSG_SIZ);
2005         SetWidgetText(&chatOptions[CHAT_OUT], "", -1); // clear text if we alter partner
2006         SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg); // clear text if we alter partner
2007         SetWidgetLabel(&chatOptions[activePartner+1], chatPartner[activePartner][0] ? chatPartner[activePartner] : _("New Chat"));
2008         if(!*partner) PaneSwitch();
2009         HardSetFocus(&chatOptions[CHAT_IN], 0);
2010     }
2011     if(line[0] || hidden) { // something was typed (for ICS commands we also allow empty line!)
2012         SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg);
2013         // from here on it could be back-end
2014         if(line[strlen(line)-1] == '\n') line[strlen(line)-1] = NULLCHAR;
2015         SaveInHistory(line);
2016         if(hidden || !*chatPartner[activePartner]) snprintf(buf, MSG_SIZ, "%s\n", line); else // command for ICS
2017         if(!strcmp("whispers", chatPartner[activePartner]))
2018               snprintf(buf, MSG_SIZ, "whisper %s\n", line); // WHISPER box uses "whisper" to send
2019         else if(!strcmp("shouts", chatPartner[activePartner]))
2020               snprintf(buf, MSG_SIZ, "shout %s\n", line); // SHOUT box uses "shout" to send
2021         else if(!strcmp("c-shouts", chatPartner[activePartner]))
2022               snprintf(buf, MSG_SIZ, "cshout %s\n", line); // C-SHOUT box uses "cshout" to send
2023         else if(!strcmp("kibitzes", chatPartner[activePartner]))
2024               snprintf(buf, MSG_SIZ, "kibitz %s\n", line); // KIBITZ box uses "kibitz" to send
2025         else {
2026             if(!atoi(chatPartner[activePartner])) {
2027                 snprintf(buf, MSG_SIZ, "> %s\n", line); // echo only tells to handle, not channel
2028                 OutputChatMessage(activePartner, buf);
2029                 snprintf(buf, MSG_SIZ, "xtell %s %s\n", chatPartner[activePartner], line);
2030             } else
2031                 snprintf(buf, MSG_SIZ, "tell %s %s\n", chatPartner[activePartner], line);
2032         }
2033         SendToICS(buf);
2034     }
2035     return FALSE; // never pop down
2036 }
2037
2038 void
2039 DelayedSetText ()
2040 {
2041     SetWidgetText(&chatOptions[CHAT_IN], tmpLine, -1); // leave focus on chat-partner field!
2042     SetInsertPos(&chatOptions[CHAT_IN], strlen(tmpLine));
2043 }
2044
2045 void
2046 DelayedScroll ()
2047 {   // If we do this immediately it does it before shrinking the memo, so the lower half remains hidden (Ughh!)
2048     SetInsertPos(&chatOptions[CHAT_ICS], 999999);
2049     SetWidgetText(&chatOptions[CHAT_IN], tmpLine, ChatDlg);
2050     SetInsertPos(&chatOptions[CHAT_IN], strlen(tmpLine));
2051 }
2052
2053 void
2054 ChatSwitch (int n)
2055 {
2056     int i, j;
2057     char *v;
2058     if(chatOptions[CHAT_ICS].type == Skip) hidden = 0; // In Xaw there is no ICS pane we can hide behind
2059     Show(&chatOptions[CHAT_PANE], 0); // show
2060     if(hidden) ScheduleDelayedEvent(DelayedScroll, 50); // Awful!
2061     else ScheduleDelayedEvent(DelayedSetText, 50);
2062     GetWidgetText(&chatOptions[CHAT_IN], &v);
2063     if(hidden) { ASSIGN(icsLine, v); } else { ASSIGN(inputs[activePartner], v); }
2064     hidden = 0;
2065     activePartner = --n;
2066     if(!texts[n]) texts[n] = strdup("");
2067     dirty[n] = 0;
2068     SetWidgetText(&chatOptions[CHAT_OUT], texts[n], ChatDlg);
2069     SetInsertPos(&chatOptions[CHAT_OUT], strlen(texts[n]));
2070     SetWidgetText(&chatOptions[CHAT_PARTNER], chatPartner[n], ChatDlg);
2071     for(i=j=0; i<MAX_CHAT; i++) {
2072         SetWidgetLabel(&chatOptions[++j], *chatPartner[i] ? chatPartner[i] : _("New Chat"));
2073         SetColor(dirty[i] ? "#FFC000" : "#FFFFFF", &chatOptions[j]);
2074     }
2075     if(!inputs[n]) { ASSIGN(inputs[n], ""); }
2076 //    SetWidgetText(&chatOptions[CHAT_IN], inputs[n], ChatDlg); // does not work (in this widget only)
2077 //    SetInsertPos(&chatOptions[CHAT_IN], strlen(inputs[n]));
2078     tmpLine = inputs[n]; // for the delayed event
2079     HardSetFocus(&chatOptions[strcmp(chatPartner[n], "") ? CHAT_IN : CHAT_PARTNER], 0);
2080 }
2081
2082 void
2083 PaneSwitch ()
2084 {
2085     char *v;
2086     Show(&chatOptions[CHAT_PANE], hidden = 1); // hide
2087     GetWidgetText(&chatOptions[CHAT_IN], &v);
2088     ASSIGN(inputs[activePartner], v);
2089     if(!icsLine) { ASSIGN(icsLine, ""); }
2090     tmpLine = icsLine; ScheduleDelayedEvent(DelayedSetText, 50);
2091 //    SetWidgetText(&chatOptions[CHAT_IN], icsLine, ChatDlg); // does not work (in this widget only)
2092 //    SetInsertPos(&chatOptions[CHAT_IN], strlen(icsLine));
2093 }
2094
2095 void
2096 ClearChat ()
2097 {   // clear the chat to make it free for other use
2098     chatPartner[activePartner][0] = NULLCHAR;
2099     ASSIGN(texts[activePartner], "");
2100     ASSIGN(inputs[activePartner], "");
2101     SetWidgetText(&chatOptions[CHAT_PARTNER], "", ChatDlg);
2102     SetWidgetText(&chatOptions[CHAT_OUT], "", ChatDlg);
2103     SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg);
2104     SetWidgetLabel(&chatOptions[activePartner+1], _("New Chat"));
2105     HardSetFocus(&chatOptions[CHAT_PARTNER], 0);
2106 }
2107
2108 static void
2109 NewChat (char *name)
2110 {   // open a chat on program request. If no empty one available, use last
2111     int i;
2112     for(i=0; i<MAX_CHAT-1; i++) if(!chatPartner[i][0]) break;
2113     safeStrCpy(chatPartner[i], name, MSG_SIZ);
2114     ChatSwitch(i+1);
2115 }
2116
2117 void
2118 ConsoleWrite(char *message, int count)
2119 {
2120     if(shellUp[ChatDlg] && chatOptions[CHAT_ICS].type != Skip) { // in Xaw this is a no-op
2121         AppendColorized(&chatOptions[CHAT_ICS], message, count);
2122         SetInsertPos(&chatOptions[CHAT_ICS], 999999);
2123     }
2124 }
2125
2126 void
2127 ChatPopUp ()
2128 {
2129     if(GenericPopUp(chatOptions, _("ICS Interaction"), ChatDlg, BoardWindow, NONMODAL, appData.topLevel))
2130         AddHandler(&chatOptions[CHAT_PARTNER], ChatDlg, 2), AddHandler(&chatOptions[CHAT_IN], ChatDlg, 2); // treats return as OK
2131     Show(&chatOptions[CHAT_PANE], hidden = 1); // hide
2132 //    HardSetFocus(&chatOptions[CHAT_IN], 0);
2133     MarkMenu("View.OpenChatWindow", ChatDlg);
2134     CursorAtEnd(&chatOptions[CHAT_IN]);
2135 }
2136
2137 void
2138 ChatProc ()
2139 {
2140     if(shellUp[ChatDlg]) PopDown(ChatDlg);
2141     else ChatPopUp();
2142 }
2143
2144 void
2145 ConsoleAutoPopUp (char *buf)
2146 {
2147         if(*buf == 27) { if(appData.icsActive && DialogExists(ChatDlg)) HardSetFocus (&chatOptions[CHAT_IN], ChatDlg); return; }
2148         if(!appData.autoBox) return;
2149         if(appData.icsActive) { // text typed to board in ICS mode: divert to ICS input box
2150             if(DialogExists(ChatDlg)) { // box already exists: append to current contents
2151                 char *p, newText[MSG_SIZ];
2152                 GetWidgetText(&chatOptions[CHAT_IN], &p);
2153                 snprintf(newText, MSG_SIZ, "%s%c", p, *buf);
2154                 SetWidgetText(&chatOptions[CHAT_IN], newText, ChatDlg);
2155                 if(shellUp[ChatDlg]) HardSetFocus (&chatOptions[CHAT_IN], ChatDlg); //why???
2156             } else { ASSIGN(line, buf); } // box did not exist: make sure it pops up with char in it
2157             ChatPopUp();
2158         } else PopUpMoveDialog(*buf);
2159 }
2160
2161 void
2162 EchoOn ()
2163 {
2164     if(!noEcho) return;
2165     system("stty echo");
2166     WidgetEcho(&chatOptions[CHAT_IN], 1);
2167     noEcho = False;
2168 }
2169
2170 void
2171 EchoOff ()
2172 {
2173     system("stty -echo");
2174     WidgetEcho(&chatOptions[CHAT_IN], 0);
2175     noEcho = True;
2176 }
2177
2178 //--------------------------------- Game-List options dialog ------------------------------------------
2179
2180 char *strings[LPUSERGLT_SIZE];
2181 int stringPtr;
2182
2183 void
2184 GLT_ClearList ()
2185 {
2186     strings[0] = NULL;
2187     stringPtr = 0;
2188 }
2189
2190 void
2191 GLT_AddToList (char *name)
2192 {
2193     strings[stringPtr++] = name;
2194     strings[stringPtr] = NULL;
2195 }
2196
2197 Boolean
2198 GLT_GetFromList (int index, char *name)
2199 {
2200   safeStrCpy(name, strings[index], MSG_SIZ);
2201   return TRUE;
2202 }
2203
2204 void
2205 GLT_DeSelectList ()
2206 {
2207 }
2208
2209 static void GLT_Button P((int n));
2210 static int GLT_OK P((int n));
2211
2212 static Option listOptions[] = {
2213 {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
2214 { 0,    0,     0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("factory") },
2215 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("up") },
2216 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("down") },
2217 { 0, SAME_ROW, 0, NULL, (void*) &GLT_OK, "", NULL, EndMark , "" }
2218 };
2219
2220 static int
2221 GLT_OK (int n)
2222 {
2223     GLT_ParseList();
2224     appData.gameListTags = strdup(lpUserGLT);
2225     GameListUpdate();
2226     return 1;
2227 }
2228
2229 static void
2230 GLT_Button (int n)
2231 {
2232     int index = SelectedListBoxItem (&listOptions[0]);
2233     char *p;
2234     if (index < 0) {
2235         DisplayError(_("No tag selected"), 0);
2236         return;
2237     }
2238     p = strings[index];
2239     if (n == 3) {
2240         if(index >= strlen(GLT_ALL_TAGS)) return;
2241         strings[index] = strings[index+1];
2242         strings[++index] = p;
2243         LoadListBox(&listOptions[0], "?", index, index-1); // only change the two specified entries
2244     } else
2245     if (n == 2) {
2246         if(index == 0) return;
2247         strings[index] = strings[index-1];
2248         strings[--index] = p;
2249         LoadListBox(&listOptions[0], "?", index, index+1);
2250     } else
2251     if (n == 1) {
2252       safeStrCpy(lpUserGLT, GLT_DEFAULT_TAGS, LPUSERGLT_SIZE);
2253       GLT_TagsToList(lpUserGLT);
2254       index = 0;
2255       LoadListBox(&listOptions[0], "?", -1, -1);
2256     }
2257     HighlightListBoxItem(&listOptions[0], index);
2258 }
2259
2260 void
2261 GameListOptionsPopUp (DialogClass parent)
2262 {
2263     safeStrCpy(lpUserGLT, appData.gameListTags, LPUSERGLT_SIZE);
2264     GLT_TagsToList(lpUserGLT);
2265
2266     GenericPopUp(listOptions, _("Game-list options"), TransientDlg, parent, MODAL, 0);
2267 }
2268
2269 void
2270 GameListOptionsProc ()
2271 {
2272     GameListOptionsPopUp(BoardWindow);
2273 }
2274
2275 //----------------------------- Error popup in various uses -----------------------------
2276
2277 /*
2278  * [HGM] Note:
2279  * XBoard has always had some pathologic behavior with multiple simultaneous error popups,
2280  * (which can occur even for modal popups when asynchrounous events, e.g. caused by engine, request a popup),
2281  * and this new implementation reproduces that as well:
2282  * Only the shell of the last instance is remembered in shells[ErrorDlg] (which replaces errorShell),
2283  * so that PopDowns ordered from the code always refer to that instance, and once that is down,
2284  * have no clue as to how to reach the others. For the Delete Window button calling PopDown this
2285  * has now been repaired, as the action routine assigned to it gets the shell passed as argument.
2286  */
2287
2288 int errorUp = False;
2289
2290 void
2291 ErrorPopDown ()
2292 {
2293     if (!errorUp) return;
2294     dialogError = errorUp = False;
2295     PopDown(ErrorDlg); PopDown(FatalDlg); // on explicit request we pop down any error dialog
2296     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
2297 }
2298
2299 int
2300 ErrorOK (int n)
2301 {
2302     dialogError = errorUp = False;
2303     PopDown(n == 1 ? FatalDlg : ErrorDlg); // kludge: non-modal dialogs have one less (dummy) option
2304     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
2305     return FALSE; // prevent second Popdown !
2306 }
2307
2308 static Option errorOptions[] = {
2309 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // dummy option: will never be displayed
2310 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // textValue field will be set before popup
2311 { 0,NO_CANCEL,0, NULL, (void*) &ErrorOK, "", NULL, EndMark , "" }
2312 };
2313
2314 void
2315 ErrorPopUp (char *title, char *label, int modal)
2316 {
2317     errorUp = True;
2318     errorOptions[1].name = label;
2319     if(dialogError = shellUp[TransientDlg])
2320         GenericPopUp(errorOptions+1, title, FatalDlg, TransientDlg, MODAL, 0); // pop up as daughter of the transient dialog
2321     else if(dialogError = shellUp[MasterDlg])
2322         GenericPopUp(errorOptions+1, title, FatalDlg, MasterDlg, MODAL, 0); // pop up as daughter of the master dialog
2323     else
2324         GenericPopUp(errorOptions+modal, title, modal ? FatalDlg: ErrorDlg, BoardWindow, modal, 0); // kludge: option start address indicates modality
2325 }
2326
2327 void
2328 DisplayError (String message, int error)
2329 {
2330     char buf[MSG_SIZ];
2331
2332     if (error == 0) {
2333         if (appData.debugMode || appData.matchMode) {
2334             fprintf(stderr, "%s: %s\n", programName, message);
2335         }
2336     } else {
2337         if (appData.debugMode || appData.matchMode) {
2338             fprintf(stderr, "%s: %s: %s\n",
2339                     programName, message, strerror(error));
2340         }
2341         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
2342         message = buf;
2343     }
2344     ErrorPopUp(_("Error"), message, FALSE);
2345 }
2346
2347
2348 void
2349 DisplayMoveError (String message)
2350 {
2351     fromX = fromY = -1;
2352     ClearHighlights();
2353     DrawPosition(TRUE, NULL); // selective redraw would miss the from-square of the rejected move, displayed empty after drag, but not marked damaged!
2354     if (appData.debugMode || appData.matchMode) {
2355         fprintf(stderr, "%s: %s\n", programName, message);
2356     }
2357     if (appData.popupMoveErrors) {
2358         ErrorPopUp(_("Error"), message, FALSE);
2359     } else {
2360         DisplayMessage(message, "");
2361     }
2362 }
2363
2364
2365 void
2366 DisplayFatalError (String message, int error, int status)
2367 {
2368     char buf[MSG_SIZ];
2369
2370     if(status == 666) { // ignore this error when ICS Console window is up
2371         if(shellUp[ChatDlg]) return;
2372         status = 0;
2373     }
2374
2375     errorExitStatus = status;
2376     if (error == 0) {
2377         fprintf(stderr, "%s: %s\n", programName, message);
2378     } else {
2379         fprintf(stderr, "%s: %s: %s\n",
2380                 programName, message, strerror(error));
2381         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
2382         message = buf;
2383     }
2384     if(mainOptions[W_BOARD].handle) {
2385         if (appData.popupExitMessage) {
2386             if(appData.icsActive) SendToICS("logout\n"); // [HGM] make sure no new games will be started
2387             ErrorPopUp(status ? _("Fatal Error") : _("Exiting"), message, TRUE);
2388         } else {
2389             ExitEvent(status);
2390         }
2391     }
2392 }
2393
2394 void
2395 DisplayInformation (String message)
2396 {
2397     ErrorPopDown();
2398     ErrorPopUp(_("Information"), message, TRUE);
2399 }
2400
2401 void
2402 DisplayNote (String message)
2403 {
2404     ErrorPopDown();
2405     ErrorPopUp(_("Note"), message, FALSE);
2406 }
2407
2408 void
2409 DisplayTitle (char *text)
2410 {
2411     char title[MSG_SIZ];
2412     char icon[MSG_SIZ];
2413
2414     if (text == NULL) text = "";
2415
2416     if(partnerUp) { SetDialogTitle(DummyDlg, text); return; }
2417
2418     if (*text != NULLCHAR) {
2419       safeStrCpy(icon, text, sizeof(icon)/sizeof(icon[0]) );
2420       safeStrCpy(title, text, sizeof(title)/sizeof(title[0]) );
2421     } else if (appData.icsActive) {
2422         snprintf(icon, sizeof(icon), "%s", appData.icsHost);
2423         snprintf(title, sizeof(title), "%s: %s", programName, appData.icsHost);
2424     } else if (appData.cmailGameName[0] != NULLCHAR) {
2425         snprintf(icon, sizeof(icon), "%s", "CMail");
2426         snprintf(title,sizeof(title), "%s: %s", programName, "CMail");
2427 #ifdef GOTHIC
2428     // [HGM] license: This stuff should really be done in back-end, but WinBoard already had a pop-up for it
2429     } else if (gameInfo.variant == VariantGothic) {
2430       safeStrCpy(icon,  programName, sizeof(icon)/sizeof(icon[0]) );
2431       safeStrCpy(title, GOTHIC,     sizeof(title)/sizeof(title[0]) );
2432 #endif
2433 #ifdef FALCON
2434     } else if (gameInfo.variant == VariantFalcon) {
2435       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
2436       safeStrCpy(title, FALCON, sizeof(title)/sizeof(title[0]) );
2437 #endif
2438     } else if (appData.noChessProgram) {
2439       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
2440       safeStrCpy(title, programName, sizeof(title)/sizeof(title[0]) );
2441     } else {
2442       safeStrCpy(icon, first.tidy, sizeof(icon)/sizeof(icon[0]) );
2443         snprintf(title,sizeof(title), "%s: %s", programName, first.tidy);
2444     }
2445     SetWindowTitle(text, title, icon);
2446 }
2447
2448 static char *
2449 ReadLine (FILE *f)
2450 {
2451     static char buf[MSG_SIZ];
2452     int i = 0, c;
2453     while((c = fgetc(f)) != '\n') { if(c == EOF) return NULL; buf[i++] = c; }
2454     buf[i] = NULLCHAR;
2455     return buf;
2456 }
2457
2458 void
2459 GetHelpText (FILE *f, char *name)
2460 {
2461     char *line, buf[MSG_SIZ], title[MSG_SIZ], text[10000], *p = text, *q = text;
2462     int len, cnt = 0;
2463     snprintf(buf, MSG_SIZ, ".B %s", name);
2464     len = strlen(buf);
2465     for(len=1; buf[len] == ' ' || buf[len] == '-' || isalpha(buf[len]) || isdigit(buf[len]); len++);
2466     buf[len] = NULLCHAR;
2467     while(buf[--len] == ' ') buf[len] = NULLCHAR;
2468     snprintf(title, MSG_SIZ, "Help on '%s'", buf+3);
2469     while((line = ReadLine(f))) {
2470         if(!strncmp(line, buf, len) || !strncmp(line, ".SS ", 4) && !strncmp(line+4, buf+3, len-3)) {
2471             while((line = ReadLine(f)) && (cnt == 0 || strncmp(line, ".B ", 3) && strncmp(line, ".SS ", 4))) {
2472                 if(!*line) { *p++ = '\n'; *p++ = '\n'; q = p; continue; }
2473                 if(*line == '.') continue;
2474                 *p++ = ' '; cnt++;
2475                 while(*line) {
2476                     if(*line < ' ') { line++; continue;}
2477                     if(*line == ' ' && p - q > 80) *line = '\n', q = p;
2478                     *p++ = *line++;
2479                 }
2480                 if(p - text > 9900) break;
2481             }
2482             *p = NULLCHAR;
2483             ErrorPopUp(title, text, FALSE);
2484             return;
2485         }
2486     }
2487     snprintf(text, MSG_SIZ, "No help on '%s'\n", buf+3);
2488     DisplayNote(text);
2489 }
2490
2491 void
2492 DisplayHelp (char *name)
2493 {
2494     char buf[MSG_SIZ], tidy[MSG_SIZ];
2495     FILE *f;
2496     if(currentCps) {
2497         TidyProgramName(currentCps == &first ? appData.firstChessProgram : appData.secondChessProgram, "localhost", tidy);
2498         snprintf(buf, MSG_SIZ, "/usr/local/share/man/man6/%s.6", tidy);
2499     } else snprintf(buf, MSG_SIZ, "%s/man6/xboard.6", MANDIR);
2500     f = fopen(buf, "r");
2501     if(!f && currentCps) { // engine manual could be in two places
2502         snprintf(buf, MSG_SIZ, "/usr/share/man/man6/%s.6", tidy);
2503         f = fopen(buf, "r");
2504     }
2505     if(f) {
2506         GetHelpText(f, name);
2507         fclose(f);
2508     }
2509 }
2510
2511 #define PAUSE_BUTTON "P"
2512 #define PIECE_MENU_SIZE 18
2513 static String pieceMenuStrings[2][PIECE_MENU_SIZE+1] = {
2514     { N_("White"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
2515       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
2516       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
2517       N_("Empty square"), N_("Clear board"), NULL },
2518     { N_("Black"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
2519       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
2520       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
2521       N_("Empty square"), N_("Clear board"), NULL }
2522 };
2523 /* must be in same order as pieceMenuStrings! */
2524 static ChessSquare pieceMenuTranslation[2][PIECE_MENU_SIZE] = {
2525     { WhitePlay, (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2526         WhiteRook, WhiteQueen, WhiteKing, (ChessSquare) 0, WhiteAlfil,
2527         WhiteCannon, WhiteAngel, WhiteMarshall, (ChessSquare) 0,
2528         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2529     { BlackPlay, (ChessSquare) 0, BlackPawn, BlackKnight, BlackBishop,
2530         BlackRook, BlackQueen, BlackKing, (ChessSquare) 0, BlackAlfil,
2531         BlackCannon, BlackAngel, BlackMarshall, (ChessSquare) 0,
2532         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2533 };
2534
2535 #define DROP_MENU_SIZE 6
2536 static String dropMenuStrings[DROP_MENU_SIZE+1] = {
2537     "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"), N_("Queen"), NULL
2538   };
2539 /* must be in same order as dropMenuStrings! */
2540 static ChessSquare dropMenuTranslation[DROP_MENU_SIZE] = {
2541     (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2542     WhiteRook, WhiteQueen
2543 };
2544
2545 // [HGM] experimental code to pop up window just like the main window, using GenercicPopUp
2546
2547 static Option *Exp P((int n, int x, int y));
2548 void MenuCallback P((int n));
2549 void SizeKludge P((int n));
2550 static Option *LogoW P((int n, int x, int y));
2551 static Option *LogoB P((int n, int x, int y));
2552
2553 static int pmFromX = -1, pmFromY = -1;
2554 void *userLogo;
2555
2556 void
2557 DisplayLogos (Option *w1, Option *w2)
2558 {
2559         void *whiteLogo = first.programLogo, *blackLogo = second.programLogo;
2560         if(appData.autoLogo) {
2561           if(appData.noChessProgram) whiteLogo = blackLogo = NULL;
2562           if(appData.icsActive) whiteLogo = blackLogo = second.programLogo;
2563           switch(gameMode) { // pick logos based on game mode
2564             case IcsObserving:
2565                 whiteLogo = second.programLogo; // ICS logo
2566                 blackLogo = second.programLogo;
2567             default:
2568                 break;
2569             case IcsPlayingWhite:
2570                 if(!appData.zippyPlay) whiteLogo = userLogo;
2571                 blackLogo = second.programLogo; // ICS logo
2572                 break;
2573             case IcsPlayingBlack:
2574                 whiteLogo = second.programLogo; // ICS logo
2575                 blackLogo = appData.zippyPlay ? first.programLogo : userLogo;
2576                 break;
2577             case TwoMachinesPlay:
2578                 if(first.twoMachinesColor[0] == 'b') {
2579                     whiteLogo = second.programLogo;
2580                     blackLogo = first.programLogo;
2581                 }
2582                 break;
2583             case MachinePlaysWhite:
2584                 blackLogo = userLogo;
2585                 break;
2586             case MachinePlaysBlack:
2587                 whiteLogo = userLogo;
2588                 blackLogo = first.programLogo;
2589           }
2590         }
2591         DrawLogo(w1, whiteLogo);
2592         DrawLogo(w2, blackLogo);
2593 }
2594
2595 static void
2596 PMSelect (int n)
2597 {   // user callback for board context menus
2598     if (pmFromX < 0 || pmFromY < 0) return;
2599     if(n == W_DROP) DropMenuEvent(dropMenuTranslation[values[n]], pmFromX, pmFromY);
2600     else EditPositionMenuEvent(pieceMenuTranslation[n - W_MENUW][values[n]], pmFromX, pmFromY);
2601 }
2602
2603 static void
2604 CCB (int n)
2605 {
2606     shiftKey = (ShiftKeys() & 3) != 0;
2607     if(n < 0) { // button != 1
2608         n = -n;
2609         if(shiftKey && (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack)) {
2610             AdjustClock(n == W_BLACK, 1);
2611         }
2612     } else
2613     ClockClick(n == W_BLACK);
2614 }
2615
2616 Option mainOptions[] = { // description of main window in terms of generic dialog creator
2617 { 0, 0xCA, 0, NULL, NULL, "", NULL, BarBegin, "" }, // menu bar
2618   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_File") },
2619   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Edit") },
2620   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_View") },
2621   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Mode") },
2622   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Action") },
2623   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("E_ngine") },
2624   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Options") },
2625   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Help") },
2626 { 0, 0, 0, NULL, (void*)&SizeKludge, "", NULL, BarEnd, "" },
2627 { 0, LR|T2T|BORDER|SAME_ROW, 0, NULL, NULL, NULL, NULL, Label, "1" }, // optional title in window
2628 { 50,    LL|TT,            100, NULL, (void*) &LogoW, NULL, NULL, Skip, "" }, // white logo
2629 { 12,   L2L|T2T,           200, NULL, (void*) &CCB, NULL, NULL, Label, "White" }, // white clock
2630 { 13,   R2R|T2T|SAME_ROW,  200, NULL, (void*) &CCB, NULL, NULL, Label, "Black" }, // black clock
2631 { 50,    RR|TT|SAME_ROW,   100, NULL, (void*) &LogoB, NULL, NULL, Skip, "" }, // black logo
2632 { 0, LR|T2T|BORDER,        401, NULL, NULL, "", NULL, Skip, "2" }, // backup for title in window (if no room for other)
2633 { 0, LR|T2T|BORDER,        270, NULL, NULL, NULL, NULL, Label, "message", &appData.font }, // message field
2634 { 0, RR|TT|SAME_ROW,       125, NULL, NULL, "", NULL, BoxBegin, "" }, // (optional) button bar
2635   { 0,    0,     0, NULL, (void*) &ToStartEvent,  NULL, NULL, Button, N_("<<"), &appData.font },
2636   { 0, SAME_ROW, 0, NULL, (void*) &BackwardEvent, NULL, NULL, Button, N_("<"),  &appData.font },
2637   { 0, SAME_ROW, 0, NULL, (void*) &PauseEvent,    NULL, NULL, Button, N_(PAUSE_BUTTON), &appData.font },
2638   { 0, SAME_ROW, 0, NULL, (void*) &ForwardEvent,  NULL, NULL, Button, N_(">"),  &appData.font },
2639   { 0, SAME_ROW, 0, NULL, (void*) &ToEndEvent,    NULL, NULL, Button, N_(">>"), &appData.font },
2640 { 0, 0, 0, NULL, NULL, "", NULL, BoxEnd, "" },
2641 { 401, LR|TB, 401, NULL, (char*) &Exp, NULL, NULL, Graph, "shadow board" }, // board
2642   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[0], PopUp, "menuW" },
2643   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[1], PopUp, "menuB" },
2644   { -1, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, dropMenuStrings, PopUp, "menuD" },
2645 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2646 };
2647
2648 Option *
2649 LogoW (int n, int x, int y)
2650 {
2651     if(n == 10) DisplayLogos(&mainOptions[W_WHITE-1], NULL);
2652     return NULL;
2653 }
2654
2655 Option *
2656 LogoB (int n, int x, int y)
2657 {
2658     if(n == 10) DisplayLogos(NULL, &mainOptions[W_BLACK+1]);
2659     return NULL;
2660 }
2661
2662 void
2663 SizeKludge (int n)
2664 {   // callback called by GenericPopUp immediately after sizing the menu bar
2665     int width = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2666     int w = width - 44 - mainOptions[n].min;
2667     mainOptions[W_TITLE].max = w; // width left behind menu bar
2668     if(w < 0.4*width) // if no reasonable amount of space for title, force small layout
2669         mainOptions[W_SMALL].type = mainOptions[W_TITLE].type, mainOptions[W_TITLE].type = Skip;
2670 }
2671
2672 void
2673 MenuCallback (int n)
2674 {
2675     MenuProc *proc = (MenuProc *) (((MenuItem*)(mainOptions[n].choice))[values[n]].proc);
2676
2677     if(!proc) RecentEngineEvent(values[n] - firstEngineItem); else (proc)();
2678 }
2679
2680 static Option *
2681 Exp (int n, int x, int y)
2682 {
2683     static int but1, but3, oldW, oldH, oldX, oldY;
2684     int menuNr = -3, sizing, f, r;
2685     TimeMark now;
2686     extern Boolean right;
2687
2688     if(right) {  // kludgy way to let button 1 double as button 3 when back-end requests this
2689         if(but1 && n == 0) but1 = 0, but3 = 1;
2690         else if(n == -1) n = -3, right = FALSE;
2691     }
2692
2693     if(n == 0) { // motion
2694         oldX = x; oldY = y;
2695         if(SeekGraphClick(Press, x, y, 1)) return NULL;
2696         if((but1 || dragging == 2) && !PromoScroll(x, y)) DragPieceMove(x, y);
2697         if(but3) MovePV(x, y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
2698         if(appData.highlightDragging) {
2699             f = EventToSquare(x, BOARD_WIDTH);  if ( flipView && f >= 0) f = BOARD_WIDTH - 1 - f;
2700             r = EventToSquare(y, BOARD_HEIGHT); if (!flipView && r >= 0) r = BOARD_HEIGHT - 1 - r;
2701             HoverEvent(x, y, f, r);
2702         }
2703         return NULL;
2704     }
2705     if(n != 10 && PopDown(PromoDlg)) fromX = fromY = -1; // user starts fiddling with board when promotion dialog is up
2706     else GetTimeMark(&now);
2707     shiftKey = ShiftKeys();
2708     controlKey = (shiftKey & 0xC) != 0;
2709     shiftKey = (shiftKey & 3) != 0;
2710     switch(n) {
2711         case  1: LeftClick(Press,   x, y), but1 = 1; break;
2712         case -1: LeftClick(Release, x, y), but1 = 0; break;
2713         case  2: shiftKey = !shiftKey;
2714         case  3: menuNr = RightClick(Press,   x, y, &pmFromX, &pmFromY), but3 = 1; break;
2715         case -2: shiftKey = !shiftKey;
2716         case -3: menuNr = RightClick(Release, x, y, &pmFromX, &pmFromY), but3 = 0; break;
2717         case  4: Wheel(-1, oldX, oldY); break;
2718         case  5: Wheel(1, oldX, oldY); break;
2719         case 10:
2720             sizing = (oldW != x || oldH != y);
2721             oldW = x; oldH = y;
2722             InitDrawingHandle(mainOptions + W_BOARD);
2723             if(sizing && SubtractTimeMarks(&now, &programStartTime) > 10000) return NULL; // don't redraw while sizing (except at startup)
2724             DrawPosition(True, NULL);
2725         default:
2726             return NULL;
2727     }
2728
2729     switch(menuNr) {
2730       case 0: return &mainOptions[shiftKey ? W_MENUW: W_MENUB];
2731       case 1: SetupDropMenu(); return &mainOptions[W_DROP];
2732       case 2:
2733       case -1: ErrorPopDown();
2734       case -2:
2735       default: break; // -3, so no clicks caught
2736     }
2737     return NULL;
2738 }
2739
2740 Option *
2741 BoardPopUp (int squareSize, int lineGap, void *clockFontThingy)
2742 {
2743     int i, size = BOARD_WIDTH*(squareSize + lineGap) + lineGap, logo = appData.logoSize;
2744     int f = 2*appData.fixedSize; // width fudge, needed for unknown reasons to not clip board
2745     mainOptions[W_WHITE].choice = (char**) clockFontThingy;
2746     mainOptions[W_BLACK].choice = (char**) clockFontThingy;
2747     mainOptions[W_BOARD].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2748     mainOptions[W_BOARD].max = mainOptions[W_SMALL].max = size; // board size
2749     mainOptions[W_SMALL].max = size - 2; // board title (subtract border!)
2750     mainOptions[W_BLACK].max = mainOptions[W_WHITE].max = size/2-3; // clock width
2751     mainOptions[W_MESSG].max = appData.showButtonBar ? size-135+f : size-2+f; // message
2752     mainOptions[W_MENU].max = size-40; // menu bar
2753     mainOptions[W_TITLE].type = appData.titleInWindow ? Label : Skip ;
2754     if(logo && logo <= size/4) { // Activate logos
2755         mainOptions[W_WHITE-1].type = mainOptions[W_BLACK+1].type = Graph;
2756         mainOptions[W_WHITE-1].max  = mainOptions[W_BLACK+1].max  = logo;
2757         mainOptions[W_WHITE-1].value= mainOptions[W_BLACK+1].value= logo/2;
2758         mainOptions[W_WHITE].min  |= SAME_ROW;
2759         mainOptions[W_WHITE].max  = mainOptions[W_BLACK].max  -= logo + 4;
2760         mainOptions[W_WHITE].name = mainOptions[W_BLACK].name = "Double\nHeight";
2761     }
2762     if(!appData.showButtonBar) for(i=W_BUTTON; i<W_BOARD; i++) mainOptions[i].type = Skip;
2763     for(i=0; i<8; i++) mainOptions[i+1].choice = (char**) menuBar[i].mi;
2764     AppendEnginesToMenu(appData.recentEngineList);
2765     GenericPopUp(mainOptions, "XBoard", BoardWindow, BoardWindow, NONMODAL, 1); // allways top-level
2766     return mainOptions;
2767 }
2768
2769 static Option *
2770 SlaveExp (int n, int x, int y)
2771 {
2772     if(n == 10) { // expose event
2773         flipView = !flipView; partnerUp = !partnerUp;
2774         DrawPosition(True, NULL); // [HGM] dual: draw other board in other orientation
2775         flipView = !flipView; partnerUp = !partnerUp;
2776     }
2777     return NULL;
2778 }
2779
2780 Option dualOptions[] = { // auxiliary board window
2781 { 0, L2L|T2T,              198, NULL, NULL, NULL, NULL, Label, "White" }, // white clock
2782 { 0, R2R|T2T|SAME_ROW,     198, NULL, NULL, NULL, NULL, Label, "Black" }, // black clock
2783 { 0, LR|T2T|BORDER,        401, NULL, NULL, NULL, NULL, Label, "This feature is experimental" }, // message field
2784 { 401, LR|TT, 401, NULL, (char*) &SlaveExp, NULL, NULL, Graph, "shadow board" }, // board
2785 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2786 };
2787
2788 void
2789 SlavePopUp ()
2790 {
2791     int size = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2792     // copy params from main board
2793     dualOptions[0].choice = mainOptions[W_WHITE].choice;
2794     dualOptions[1].choice = mainOptions[W_BLACK].choice;
2795     dualOptions[3].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2796     dualOptions[3].max = dualOptions[2].max = size; // board width
2797     dualOptions[0].max = dualOptions[1].max = size/2 - 3; // clock width
2798     GenericPopUp(dualOptions, "XBoard", DummyDlg, BoardWindow, NONMODAL, appData.topLevel);
2799     SlaveResize(dualOptions+3);
2800 }
2801
2802 void
2803 DisplayWhiteClock (long timeRemaining, int highlight)
2804 {
2805     if(appData.noGUI) return;
2806     if(twoBoards && partnerUp) {
2807         DisplayTimerLabel(&dualOptions[0], _("White"), timeRemaining, highlight);
2808         return;
2809     }
2810     DisplayTimerLabel(&mainOptions[W_WHITE], _("White"), timeRemaining, highlight);
2811     if(highlight) SetClockIcon(0);
2812 }
2813
2814 void
2815 DisplayBlackClock (long timeRemaining, int highlight)
2816 {
2817     if(appData.noGUI) return;
2818     if(twoBoards && partnerUp) {
2819         DisplayTimerLabel(&dualOptions[1], _("Black"), timeRemaining, highlight);
2820         return;
2821     }
2822     DisplayTimerLabel(&mainOptions[W_BLACK], _("Black"), timeRemaining, highlight);
2823     if(highlight) SetClockIcon(1);
2824 }
2825
2826
2827 //---------------------------------------------
2828
2829 void
2830 DisplayMessage (char *message, char *extMessage)
2831 {
2832   /* display a message in the message widget */
2833
2834   char buf[MSG_SIZ];
2835
2836   if (extMessage)
2837     {
2838       if (*message)
2839         {
2840           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
2841           message = buf;
2842         }
2843       else
2844         {
2845           message = extMessage;
2846         };
2847     };
2848
2849     safeStrCpy(lastMsg, message, MSG_SIZ); // [HGM] make available
2850
2851   /* need to test if messageWidget already exists, since this function
2852      can also be called during the startup, if for example a Xresource
2853      is not set up correctly */
2854   if(mainOptions[W_MESSG].handle)
2855     SetWidgetLabel(&mainOptions[W_MESSG], message);
2856
2857   return;
2858 }
2859
2860 //----------------------------------- File Browser -------------------------------
2861
2862 #ifdef HAVE_DIRENT_H
2863 #include <dirent.h>
2864 #else
2865 #include <sys/dir.h>
2866 #define dirent direct
2867 #endif
2868
2869 #include <sys/stat.h>
2870
2871 #define MAXFILES 1000
2872
2873 static DialogClass savDlg;
2874 static ChessProgramState *savCps;
2875 static FILE **savFP;
2876 static char *fileName, *extFilter, *savMode, **namePtr;
2877 static int folderPtr, filePtr, oldVal, byExtension, extFlag, pageStart, cnt;
2878 static char curDir[MSG_SIZ], title[MSG_SIZ], *folderList[MAXFILES], *fileList[MAXFILES];
2879
2880 static char *FileTypes[] = {
2881 "Chess Games",
2882 "Chess Positions",
2883 "Tournaments",
2884 "Opening Books",
2885 "Sound files",
2886 "Images",
2887 "Settings (*.ini)",
2888 "Log files",
2889 "All files",
2890 NULL,
2891 "PGN",
2892 "Old-Style Games",
2893 "FEN",
2894 "Old-Style Positions",
2895 NULL,
2896 NULL
2897 };
2898
2899 static char *Extensions[] = {
2900 ".pgn .game",
2901 ".fen .epd .pos",
2902 ".trn",
2903 ".bin",
2904 ".wav",
2905 ".png",
2906 ".ini",
2907 ".log",
2908 "",
2909 "INVALID",
2910 ".pgn",
2911 ".game",
2912 ".fen",
2913 ".pos",
2914 NULL,
2915 ""
2916 };
2917
2918 void DirSelProc P((int n, int sel));
2919 void FileSelProc P((int n, int sel));
2920 void SetTypeFilter P((int n));
2921 int BrowseOK P((int n));
2922 void Switch P((int n));
2923 void CreateDir P((int n));
2924
2925 Option browseOptions[] = {
2926 {   0,    LR|T2T,      500, NULL, NULL, NULL, NULL, Label, title },
2927 {   0,    L2L|T2T,     250, NULL, NULL, NULL, NULL, Label, N_("Directories:") },
2928 {   0,R2R|T2T|SAME_ROW,100, NULL, NULL, NULL, NULL, Label, N_("Files:") },
2929 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by name") },
2930 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by type") },
2931 { 300,    L2L|TB,      250, NULL, (void*) folderList, (char*) &DirSelProc, NULL, ListBox, "" },
2932 { 300, R2R|TB|SAME_ROW,250, NULL, (void*) fileList, (char*) &FileSelProc, NULL, ListBox, "" },
2933 {   0,       0,        300, NULL, (void*) &fileName, NULL, NULL, TextBox, N_("Filename:") },
2934 {   0,    SAME_ROW,    120, NULL, (void*) &CreateDir, NULL, NULL, Button, N_("New directory") },
2935 {   0, COMBO_CALLBACK, 150, NULL, (void*) &SetTypeFilter, NULL, FileTypes, ComboBox, N_("File type:") },
2936 {   0,    SAME_ROW,      0, NULL, (void*) &BrowseOK, "", NULL, EndMark , "" }
2937 };
2938
2939 int
2940 BrowseOK (int n)
2941 {
2942         if(!fileName[0]) { // it is enough to have a file selected
2943             if(browseOptions[6].textValue) { // kludge: if callback specified we browse for file
2944                 int sel = SelectedListBoxItem(&browseOptions[6]);
2945                 if(sel < 0 || sel >= filePtr) return FALSE;
2946                 ASSIGN(fileName, fileList[sel]);
2947             } else { // we browse for path
2948                 ASSIGN(fileName, curDir); // kludge: without callback we browse for path
2949             }
2950         }
2951         if(!fileName[0]) return FALSE; // refuse OK when no file
2952         if(!savMode[0]) { // browsing for name only (dialog Browse button)
2953                 if(fileName[0] == '/') // We already had a path name
2954                     snprintf(title, MSG_SIZ, "%s", fileName);
2955                 else
2956                     snprintf(title, MSG_SIZ, "%s/%s", curDir, fileName);
2957                 SetWidgetText((Option*) savFP, title, savDlg);
2958                 currentCps = savCps; // could return to Engine Settings dialog!
2959                 return TRUE;
2960         }
2961         *savFP = fopen(fileName, savMode);
2962         if(*savFP == NULL) return FALSE; // refuse OK if file not openable
2963         ASSIGN(*namePtr, fileName);
2964         ScheduleDelayedEvent(DelayedLoad, 50);
2965         currentCps = savCps; // not sure this is ever non-null
2966         return TRUE;
2967 }
2968
2969 int
2970 AlphaNumCompare (char *p, char *q)
2971 {
2972     while(*p) {
2973         if(isdigit(*p) && isdigit(*q) && atoi(p) != atoi(q))
2974              return (atoi(p) > atoi(q) ? 1 : -1);
2975         if(*p != *q) break;
2976         p++, q++;
2977     }
2978     if(*p == *q) return 0;
2979     return (*p > *q ? 1 : -1);
2980 }
2981
2982 int
2983 Comp (const void *s, const void *t)
2984 {
2985     char *p = *(char**) s, *q = *(char**) t;
2986     if(extFlag) {
2987         char *h; int r;
2988         while(h = strchr(p, '.')) p = h+1;
2989         if(p == *(char**) s) p = "";
2990         while(h = strchr(q, '.')) q = h+1;
2991         if(q == *(char**) t) q = "";
2992         r = AlphaNumCompare(p, q);
2993         if(r) return r;
2994     }
2995     return AlphaNumCompare( *(char**) s, *(char**) t );
2996 }
2997
2998 void
2999 ListDir (int pathFlag)
3000 {
3001         DIR *dir;
3002         struct dirent *dp;
3003         struct stat statBuf;
3004         static int lastFlag;
3005
3006         if(pathFlag < 0) pathFlag = lastFlag;
3007         lastFlag = pathFlag;
3008         dir = opendir(".");
3009         getcwd(curDir, MSG_SIZ);
3010         snprintf(title, MSG_SIZ, "%s   %s", _("Contents of"), curDir);
3011         folderPtr = filePtr = cnt = 0; // clear listing
3012
3013         while (dp = readdir(dir)) { // pass 1: list foders
3014             char *s = dp->d_name;
3015             if(!stat(s, &statBuf) && S_ISDIR(statBuf.st_mode)) { // stat succeeds and tells us it is directory
3016                 if(s[0] == '.' && strcmp(s, "..")) continue; // suppress hidden, except ".."
3017                 ASSIGN(folderList[folderPtr], s); if(folderPtr < MAXFILES-2) folderPtr++;
3018             } else if(!pathFlag) {
3019                 char *s = dp->d_name, match=0;
3020 //              if(cnt == pageStart) { ASSIGN }
3021                 if(s[0] == '.') continue; // suppress hidden files
3022                 if(extFilter[0]) { // [HGM] filter on extension
3023                     char *p = extFilter, *q;
3024                     do {
3025                         if(q = strchr(p, ' ')) *q = 0;
3026                         if(strstr(s, p)) match++;
3027                         if(q) *q = ' ';
3028                     } while(q && (p = q+1));
3029                     if(!match) continue;
3030                 }
3031                 if(filePtr == MAXFILES-2) continue;
3032                 if(cnt++ < pageStart) continue;
3033                 ASSIGN(fileList[filePtr], s); filePtr++;
3034             }
3035         }
3036         if(filePtr == MAXFILES-2) { ASSIGN(fileList[filePtr], _("  next page")); filePtr++; }
3037         FREE(folderList[folderPtr]); folderList[folderPtr] = NULL;
3038         FREE(fileList[filePtr]); fileList[filePtr] = NULL;
3039         closedir(dir);
3040         extFlag = 0;         qsort((void*)folderList, folderPtr, sizeof(char*), &Comp);
3041         extFlag = byExtension; qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
3042 }
3043
3044 void
3045 Refresh (int pathFlag)
3046 {
3047     ListDir(pathFlag); // and make new one
3048     LoadListBox(&browseOptions[5], "", -1, -1);
3049     LoadListBox(&browseOptions[6], "", -1, -1);
3050     SetWidgetLabel(&browseOptions[0], title);
3051 }
3052
3053 static char msg1[] = N_("FIRST TYPE DIRECTORY NAME HERE");
3054 static char msg2[] = N_("TRY ANOTHER NAME");
3055
3056 void
3057 CreateDir (int n)
3058 {
3059     char *name, *errmsg = "";
3060     GetWidgetText(&browseOptions[n-1], &name);
3061     if(!strcmp(name, msg1) || !strcmp(name, msg2)) return;
3062     if(!name[0]) errmsg = _(msg1); else
3063     if(mkdir(name, 0755)) errmsg = _(msg2);
3064     else {
3065         chdir(name);
3066         Refresh(-1);
3067     }
3068     SetWidgetText(&browseOptions[n-1], errmsg, BrowserDlg);
3069 }
3070
3071 void
3072 Switch (int n)
3073 {
3074     if(byExtension == (n == 4)) return;
3075     extFlag = byExtension = (n == 4);
3076     qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
3077     LoadListBox(&browseOptions[6], "", -1, -1);
3078 }
3079
3080 void
3081 SetTypeFilter (int n)
3082 {
3083     int j = values[n];
3084     if(j == browseOptions[n].value) return; // no change
3085     browseOptions[n].value = j;
3086     SetWidgetLabel(&browseOptions[n], FileTypes[j]);
3087     ASSIGN(extFilter, Extensions[j]);
3088     pageStart = 0;
3089     Refresh(-1); // uses pathflag remembered by ListDir
3090     values[n] = oldVal; // do not disturb combo settings of underlying dialog
3091 }
3092
3093 void
3094 FileSelProc (int n, int sel)
3095 {
3096     if(sel < 0 || fileList[sel] == NULL) return;
3097     if(sel == MAXFILES-2) { pageStart = cnt; Refresh(-1); return; }
3098     ASSIGN(fileName, fileList[sel]);
3099     if(BrowseOK(0)) PopDown(BrowserDlg);
3100 }
3101
3102 void
3103 DirSelProc (int n, int sel)
3104 {
3105     if(!chdir(folderList[sel])) { // cd succeeded, so we are in new directory now
3106         Refresh(-1);
3107     }
3108 }
3109
3110 void
3111 StartDir (char *filter, char *newName)
3112 {
3113     static char *gamesDir, *trnDir, *imgDir, *bookDir;
3114     static char curDir[MSG_SIZ];
3115     char **res = NULL;
3116     if(!filter || !*filter) return;
3117     if(strstr(filter, "pgn")) res = &gamesDir; else
3118     if(strstr(filter, "bin")) res = &bookDir; else
3119     if(strstr(filter, "png")) res = &imgDir; else
3120     if(strstr(filter, "trn")) res = &trnDir; else
3121     if(strstr(filter, "fen")) res = &appData.positionDir;
3122     if(res) {
3123         if(newName) {
3124             char *p, *q;
3125             if(*newName) {
3126                 ASSIGN(*res, newName);
3127                 for(p=*res; q=strchr(p, '/');) p = q + 1; *p = NULLCHAR;
3128             }
3129             if(*curDir) chdir(curDir);
3130             *curDir = NULLCHAR;
3131         } else {
3132             getcwd(curDir, MSG_SIZ);
3133             if(*res && **res) chdir(*res);
3134         }
3135     }
3136 }
3137
3138 void
3139 Browse (DialogClass dlg, char *label, char *proposed, char *ext, Boolean pathFlag, char *mode, char **name, FILE **fp)
3140 {
3141     int j=0;
3142     savFP = fp; savMode = mode, namePtr = name, savCps = currentCps, oldVal = values[9], savDlg = dlg; // save params, for use in callback
3143     ASSIGN(extFilter, ext);
3144     ASSIGN(fileName, proposed ? proposed : "");
3145     for(j=0; Extensions[j]; j++) // look up actual value in list of possible values, to get selection nr
3146         if(extFilter && !strcmp(extFilter, Extensions[j])) break;
3147     if(Extensions[j] == NULL) { j++; ASSIGN(FileTypes[j], extFilter); }
3148     browseOptions[9].value = j;
3149     browseOptions[6].textValue = (char*) (pathFlag ? NULL : &FileSelProc); // disable file listbox during path browsing
3150     pageStart = 0; ListDir(pathFlag);
3151     currentCps = NULL;
3152     GenericPopUp(browseOptions, label, BrowserDlg, dlg, MODAL, 0);
3153     SetWidgetLabel(&browseOptions[9], FileTypes[j]);
3154 }
3155
3156 static char *openName;
3157 FileProc fileProc;
3158 char *fileOpenMode;
3159 FILE *openFP;
3160
3161 void
3162 DelayedLoad ()
3163 {
3164   (void) (*fileProc)(openFP, 0, openName);
3165 }
3166
3167 void
3168 FileNamePopUp (char *label, char *def, char *filter, FileProc proc, char *openMode)
3169 {
3170     fileProc = proc;            /* I can't see a way not */
3171     fileOpenMode = openMode;    /*   to use globals here */
3172     FileNamePopUpWrapper(label, def, filter, proc, False, openMode, &openName, &openFP);
3173 }
3174
3175 void
3176 ActivateTheme (int col)
3177 {
3178     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap; else lineGap = defaultLineGap;
3179     InitDrawingParams(strcmp(oldPieceDir, appData.pieceDirectory));
3180     InitDrawingSizes(-1, 0);
3181     DrawPosition(True, NULL);
3182 }
3183