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