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