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