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