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