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