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