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