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