Add Fonts dialog
[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, 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, 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     if(soundFiles[values[2]]) PlaySoundFile(soundFiles[values[2]]);
870 }
871
872 void
873 SoundOptionsProc ()
874 {
875    free(soundFiles[2]);
876    soundFiles[2] = strdup("*");
877    GenericPopUp(soundOptions, _("Sound Options"), TransientDlg, BoardWindow, MODAL, 0);
878 }
879
880 //--------------------------------------------- Board Options --------------------------------------
881
882 static void DefColor P((int n));
883 static void AdjustColor P((int i));
884 static void ThemeSel P((int n, int sel));
885 static int BoardOptionsOK P((int n));
886
887 static char oldPieceDir[MSG_SIZ];
888 extern char *engineLine, *nickName; // defined later on
889
890 #define THEMELIST 1
891
892 static Option boardOptions[] = {
893 {   0,LR|T2T, 0, NULL, NULL, NULL, NULL, Label, N_("Selectable themes:") },
894 { 300,LR|TB,200, NULL, (void*) engineMnemonic, (char*) &ThemeSel, NULL, ListBox, "" },
895 {   0,LR|T2T, 0, NULL, NULL, NULL, NULL, Label, N_("New name for current theme:") },
896 { 0, 0, 0, NULL, (void*) &nickName, "", NULL, TextBox, "" },
897 { 0,SAME_ROW, 0, NULL, NULL, NULL, NULL, Break, NULL },
898 { 0,          0, 70, NULL, (void*) &appData.whitePieceColor, "", NULL, TextBox, N_("White Piece Color:") },
899 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#FFFFCC", Button, "      " },
900 /* TRANSLATORS: R = single letter for the color red */
901 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
902 /* TRANSLATORS: G = single letter for the color green */
903 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
904 /* TRANSLATORS: B = single letter for the color blue */
905 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
906 /* TRANSLATORS: D = single letter to make a color darker */
907 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
908 { 0,          0, 70, NULL, (void*) &appData.blackPieceColor, "", NULL, TextBox, N_("Black Piece Color:") },
909 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#202020", Button, "      " },
910 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
911 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
912 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
913 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
914 { 0,          0, 70, NULL, (void*) &appData.lightSquareColor, "", NULL, TextBox, N_("Light Square Color:") },
915 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#C8C365", Button, "      " },
916 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
917 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
918 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
919 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
920 { 0,          0, 70, NULL, (void*) &appData.darkSquareColor, "", NULL, TextBox, N_("Dark Square Color:") },
921 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#77A26D", Button, "      " },
922 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
923 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
924 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
925 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
926 { 0,          0, 70, NULL, (void*) &appData.highlightSquareColor, "", NULL, TextBox, N_("Highlight Color:") },
927 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#FFFF00", Button, "      " },
928 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
929 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
930 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
931 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
932 { 0,          0, 70, NULL, (void*) &appData.premoveHighlightColor, "", NULL, TextBox, N_("Premove Highlight Color:") },
933 { 1000, SAME_ROW, 0, NULL, (void*) &DefColor, NULL, (char**) "#FF0000", Button, "      " },
934 {    1, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("R") },
935 {    2, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("G") },
936 {    3, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("B") },
937 {    4, SAME_ROW, 0, NULL, (void*) &AdjustColor, NULL, NULL, Button, N_("D") },
938 { 0, 0, 0, NULL, (void*) &appData.upsideDown, "", NULL, CheckBox, N_("Flip Pieces Shogi Style        (Colored buttons restore default)") },
939 //{ 0, 0, 0, NULL, (void*) &appData.allWhite, "", NULL, CheckBox, N_("Use Outline Pieces for Black") },
940 { 0, 0, 0, NULL, (void*) &appData.monoMode, "", NULL, CheckBox, N_("Mono Mode") },
941 { 0, 0, 200, NULL, (void*) &appData.logoSize, "", NULL, Spin, N_("Logo Size (0=off, requires restart):") },
942 { 0,-1, 5, NULL, (void*) &appData.overrideLineGap, "", NULL, Spin, N_("Line Gap (-1 = default for board size):") },
943 { 0, 0, 0, NULL, (void*) &appData.useBitmaps, "", NULL, CheckBox, N_("Use Board Textures") },
944 { 0, 0, 0, NULL, (void*) &appData.liteBackTextureFile, ".png", NULL, FileName, N_("Light-Squares Texture File:") },
945 { 0, 0, 0, NULL, (void*) &appData.darkBackTextureFile, ".png", NULL, FileName, N_("Dark-Squares Texture File:") },
946 { 0, 0, 0, NULL, (void*) &appData.trueColors, "", NULL, CheckBox, N_("Use external piece bitmaps with their own colors") },
947 { 0, 0, 0, NULL, (void*) &appData.pieceDirectory, "", NULL, PathName, N_("Directory with Pieces Images:") },
948 { 0, 0, 0, NULL, (void*) &BoardOptionsOK, "", NULL, EndMark , "" }
949 };
950
951 static int
952 BoardOptionsOK (int n)
953 {
954     if(n && (n = SelectedListBoxItem(&boardOptions[THEMELIST])) > 0 && *engineList[n] != '#') { // called by pressing OK, and theme selected
955         ASSIGN(engineLine, engineList[n]);
956     }
957     LoadTheme();
958     return 1;
959 }
960
961 static void
962 SetColorText (int n, char *buf)
963 {
964     SetWidgetText(&boardOptions[n-1], buf, TransientDlg);
965     SetColor(buf, &boardOptions[n]);
966 }
967
968 static void
969 DefColor (int n)
970 {
971     SetColorText(n, (char*) boardOptions[n].choice);
972 }
973
974 void
975 RefreshColor (int source, int n)
976 {
977     int col, j, r, g, b, step = 10;
978     char *s, buf[MSG_SIZ]; // color string
979     GetWidgetText(&boardOptions[source], &s);
980     if(sscanf(s, "#%x", &col) != 1) return;   // malformed
981     b = col & 0xFF; g = col & 0xFF00; r = col & 0xFF0000;
982     switch(n) {
983         case 1: r += 0x10000*step;break;
984         case 2: g += 0x100*step;  break;
985         case 3: b += step;        break;
986         case 4: r -= 0x10000*step; g -= 0x100*step; b -= step; break;
987     }
988     if(r < 0) r = 0; if(g < 0) g = 0; if(b < 0) b = 0;
989     if(r > 0xFF0000) r = 0xFF0000; if(g > 0xFF00) g = 0xFF00; if(b > 0xFF) b = 0xFF;
990     col = r | g | b;
991     snprintf(buf, MSG_SIZ, "#%06x", col);
992     for(j=1; j<7; j++) if(buf[j] >= 'a') buf[j] -= 32; // capitalize
993     SetColorText(source+1, buf);
994 }
995
996 static void
997 AdjustColor (int i)
998 {
999     int n = boardOptions[i].value;
1000     RefreshColor(i-n-1, n);
1001 }
1002
1003 void
1004 ThemeSel (int n, int sel)
1005 {
1006     int nr;
1007     char buf[MSG_SIZ];
1008     if(sel < 1) buf[0] = NULLCHAR; // back to top level
1009     else if(engineList[sel][0] == '#') safeStrCpy(buf, engineList[sel], MSG_SIZ); // group header, open group
1010     else { // normal line, select engine
1011         ASSIGN(engineLine, engineList[sel]);
1012         LoadTheme();
1013         PopDown(TransientDlg);
1014         return;
1015     }
1016     nr = NamesToList(appData.themeNames, engineList, engineMnemonic, buf); // replace list by only the group contents
1017     ASSIGN(engineMnemonic[0], buf);
1018     LoadListBox(&boardOptions[THEMELIST], _("# no themes are defined"), -1, -1);
1019     HighlightWithScroll(&boardOptions[THEMELIST], 0, nr);
1020 }
1021
1022 void
1023 BoardOptionsProc ()
1024 {
1025    strncpy(oldPieceDir, appData.pieceDirectory, MSG_SIZ-1); // to see if it changed
1026    ASSIGN(engineLine, "");
1027    ASSIGN(nickName, "");
1028    ASSIGN(engineMnemonic[0], "");
1029    NamesToList(appData.themeNames, engineList, engineMnemonic, "");
1030    GenericPopUp(boardOptions, _("Board Options"), TransientDlg, BoardWindow, MODAL, 0);
1031 }
1032
1033 //-------------------------------------------- ICS Text Menu Options ------------------------------
1034
1035 Option textOptions[100];
1036 static void PutText P((char *text, int pos));
1037 static void NewChat P((char *name));
1038 static char clickedWord[MSG_SIZ], click;
1039
1040 void
1041 SendString (char *p)
1042 {
1043     char buf[MSG_SIZ], buf2[MSG_SIZ], *q;
1044     if(q = strstr(p, "$name")) { // in Xaw this is already intercepted
1045         if(!shellUp[TextMenuDlg] || !clickedWord[0]) return;
1046         strncpy(buf2, p, MSG_SIZ);
1047         snprintf(buf2 + (q-p), MSG_SIZ -(q-p), "%s%s", clickedWord, q+5);
1048         p = buf2;
1049     }
1050     if(!strcmp(p, "$copy")) { // special case for copy selection
1051         CopySomething(clickedWord);
1052     } else
1053     if(!strcmp(p, "$chat")) { // special case for opening chat
1054         NewChat(clickedWord);
1055     } else
1056     if(q = strstr(p, "$input")) {
1057         if(!shellUp[TextMenuDlg]) return;
1058         strncpy(buf, p, MSG_SIZ);
1059         strncpy(buf + (q-p), q+6, MSG_SIZ-(q-p));
1060         PutText(buf, q-p);
1061     } else {
1062         snprintf(buf, MSG_SIZ, "%s\n", p);
1063         SendToICS(buf);
1064     }
1065     if(click) { // popped up by memo click
1066         click = clickedWord[0] = 0;
1067         PopDown(TextMenuDlg);
1068     }
1069 }
1070
1071 void
1072 IcsTextPopUp ()
1073 {
1074    int i=0, j;
1075    char *p, *q, *r;
1076    if((p = icsTextMenuString) == NULL) return;
1077    do {
1078         q = r = p; while(*p && *p != ';') p++;
1079         if(textOptions[i].name == NULL) textOptions[i].name = (char*) malloc(MSG_SIZ);
1080         for(j=0; j<p-q; j++) textOptions[i].name[j] = *r++;
1081         textOptions[i].name[j++] = 0;
1082         if(!*p) break;
1083         if(*++p == '\n') p++; // optional linefeed after button-text terminating semicolon
1084         q = p;
1085         textOptions[i].choice = (char**) (r = textOptions[i].name + j);
1086         while(*p && (*p != ';' || p[1] != '\n')) textOptions[i].name[j++] = *p++;
1087         textOptions[i].name[j++] = 0;
1088         if(*p) p += 2;
1089         textOptions[i].max = 135;
1090         textOptions[i].min = i&1;
1091         textOptions[i].handle = NULL;
1092         textOptions[i].target = &SendText;
1093         textOptions[i].textValue = strstr(r, "$input") ? "#80FF80" : strstr(r, "$name") ? "#FF8080" : "#FFFFFF";
1094         textOptions[i].type = Button;
1095    } while(++i < 99 && *p);
1096    if(i == 0) return;
1097    textOptions[i].type = EndMark;
1098    textOptions[i].target = NULL;
1099    textOptions[i].min = 2;
1100    MarkMenu("View.ICStextmenu", TextMenuDlg);
1101    GenericPopUp(textOptions, _("ICS text menu"), TextMenuDlg, BoardWindow, NONMODAL, appData.topLevel);
1102 }
1103
1104 void
1105 IcsTextProc ()
1106 {
1107     if(shellUp[TextMenuDlg]) PopDown(TextMenuDlg);
1108     else IcsTextPopUp();
1109 }
1110
1111 //---------------------------------------------------- Edit Comment -----------------------------------
1112
1113 static char *commentText;
1114 static int commentIndex;
1115 static void ClearComment P((int n));
1116 static void SaveChanges P((int n));
1117 int savedIndex;  /* gross that this is global (and even across files...) */
1118
1119 static int CommentClick P((Option *opt, int n, int x, int y, char *val, int index));
1120
1121 static int
1122 NewComCallback (int n)
1123 {
1124     ReplaceComment(commentIndex, commentText);
1125     return 1;
1126 }
1127
1128 Option commentOptions[] = {
1129 { 200, T_VSCRL | T_FILL | T_WRAP | T_TOP, 250, NULL, (void*) &commentText, NULL, (char **) &CommentClick, TextBox, "", &appData.commentFont },
1130 { 0,     0,     50, NULL, (void*) &ClearComment, NULL, NULL, Button, N_("clear") },
1131 { 0, SAME_ROW, 100, NULL, (void*) &SaveChanges, NULL, NULL, Button, N_("save changes") },
1132 { 0, SAME_ROW,  0,  NULL, (void*) &NewComCallback, "", NULL, EndMark , "" }
1133 };
1134
1135 static int
1136 CommentClick (Option *opt, int n, int x, int y, char *val, int index)
1137 {
1138         if(n != 3) return FALSE; // only button-3 press is of interest
1139         ReplaceComment(savedIndex, val);
1140         if(savedIndex != currentMove) ToNrEvent(savedIndex);
1141         LoadVariation( index, val ); // [HGM] also does the actual moving to it, now
1142         return TRUE;
1143 }
1144
1145 static void
1146 SaveChanges (int n)
1147 {
1148     GenericReadout(commentOptions, 0);
1149     ReplaceComment(commentIndex, commentText);
1150 }
1151
1152 static void
1153 ClearComment (int n)
1154 {
1155     SetWidgetText(&commentOptions[0], "", CommentDlg);
1156 }
1157
1158 void
1159 NewCommentPopup (char *title, char *text, int index)
1160 {
1161     if(DialogExists(CommentDlg)) { // if already exists, alter title and content
1162         SetDialogTitle(CommentDlg, title);
1163         SetWidgetText(&commentOptions[0], text, CommentDlg);
1164     }
1165     if(commentText) free(commentText); commentText = strdup(text);
1166     commentIndex = index;
1167     MarkMenu("View.Comments", CommentDlg);
1168     if(GenericPopUp(commentOptions, title, CommentDlg, BoardWindow, NONMODAL, appData.topLevel))
1169         AddHandler(&commentOptions[0], CommentDlg, 1);
1170 }
1171
1172 void
1173 EditCommentPopUp (int index, char *title, char *text)
1174 {
1175     savedIndex = index;
1176     if (text == NULL) text = "";
1177     NewCommentPopup(title, text, index);
1178 }
1179
1180 void
1181 CommentPopUp (char *title, char *text)
1182 {
1183     savedIndex = currentMove; // [HGM] vari
1184     NewCommentPopup(title, text, currentMove);
1185 }
1186
1187 void
1188 CommentPopDown ()
1189 {
1190     PopDown(CommentDlg);
1191 }
1192
1193
1194 void
1195 EditCommentProc ()
1196 {
1197     if (PopDown(CommentDlg)) { // popdown succesful
1198 //      MarkMenuItem("Edit.EditComment", False);
1199 //      MarkMenuItem("View.Comments", False);
1200     } else // was not up
1201         EditCommentEvent();
1202 }
1203
1204 //------------------------------------------------------ Edit Tags ----------------------------------
1205
1206 static void changeTags P((int n));
1207 static char *tagsText, **resPtr;
1208
1209 static int TagsClick P((Option *opt, int n, int x, int y, char *val, int index));
1210
1211 static int
1212 NewTagsCallback (int n)
1213 {
1214     if(bookUp) SaveToBook(tagsText), DisplayBook(currentMove); else
1215     if(resPtr) { ASSIGN(*resPtr, tagsText); } else
1216     ReplaceTags(tagsText, &gameInfo);
1217     return 1;
1218 }
1219
1220 static void
1221 NewMove ()
1222 {
1223     addToBookFlag = !addToBookFlag;
1224 }
1225
1226 static Option tagsOptions[] = {
1227 {   0,   0,   0, NULL, NULL, NULL, NULL, Label,  NULL },
1228 { 200, T_VSCRL | T_FILL | T_WRAP | T_TOP, 200, NULL, (void*) &tagsText, NULL, (char **) &TagsClick, TextBox, "", &appData.tagsFont },
1229 {   0,   0, 100, NULL, (void*) &NewMove,    NULL, NULL, Button, N_("add next move") },
1230 { 0,SAME_ROW,100,NULL, (void*) &changeTags, NULL, NULL, Button, N_("commit changes") },
1231 { 0,SAME_ROW, 0, NULL, (void*) &NewTagsCallback, "", NULL, EndMark , "" }
1232 };
1233
1234 static int TagsClick (Option *opt, int n, int x, int y, char *val, int index)
1235 {
1236     if(!bookUp || n != 3) return FALSE; // only button-3 press in Edit Book is of interest
1237     PlayBookMove(val, index);
1238     return TRUE;
1239 }
1240
1241 static void
1242 changeTags (int n)
1243 {
1244     GenericReadout(tagsOptions, 1);
1245     if(bookUp) SaveToBook(tagsText), DisplayBook(currentMove); else
1246     if(resPtr) { ASSIGN(*resPtr, tagsText); } else
1247     ReplaceTags(tagsText, &gameInfo);
1248 }
1249
1250 void
1251 NewTagsPopup (char *text, char *msg, char *ttl)
1252 {
1253     char *title = bookUp ? _("Edit book") : ttl;
1254
1255     tagsOptions[2].type = bookUp ? Button : Skip;
1256     tagsOptions[3].min = bookUp ? SAME_ROW : 0;
1257     if(DialogExists(TagsDlg)) { // if already exists, alter title and content
1258         SetWidgetText(&tagsOptions[1], text, TagsDlg);
1259         SetDialogTitle(TagsDlg, title);
1260     }
1261     if(tagsText) free(tagsText); tagsText = strdup(text);
1262     tagsOptions[0].name = msg;
1263     MarkMenu("View.Tags", TagsDlg);
1264     GenericPopUp(tagsOptions + (msg == NULL), title, TagsDlg, BoardWindow, NONMODAL, appData.topLevel);
1265 }
1266
1267 void
1268 TagsPopUp (char *tags, char *msg)
1269 {
1270     NewTagsPopup(tags, cmailMsgLoaded ? msg : NULL, _("Tags"));
1271 }
1272
1273 void
1274 EditTagsPopUp (char *tags, char **dest)
1275 {   // wrapper to preserve old name used in back-end
1276     resPtr = dest; 
1277     NewTagsPopup(tags, NULL, _("Tags"));
1278 }
1279
1280 void
1281 EditEnginePopUp (char *tags, char **dest)
1282 {   // wrapper to preserve old name used in back-end
1283     TagsPopDown();
1284     resPtr = dest; 
1285     NewTagsPopup(tags, NULL, _("Registered Engines"));
1286 }
1287
1288 void
1289 TagsPopDown()
1290 {
1291     PopDown(TagsDlg);
1292     bookUp = False;
1293 }
1294
1295 void
1296 EditTagsProc ()
1297 {
1298   if (bookUp || !PopDown(TagsDlg)) EditTagsEvent();
1299 }
1300
1301 void
1302 AddBookMove (char *text)
1303 {
1304     AppendText(&tagsOptions[1], text);
1305 }
1306
1307 //---------------------------------------------- ICS Input Box ----------------------------------
1308
1309 char *icsText;
1310
1311 // [HGM] code borrowed from winboard.c (which should thus go to backend.c!)
1312 #define HISTORY_SIZE 64
1313 static char *history[HISTORY_SIZE];
1314 static int histIn = 0, histP = 0;
1315 static Boolean noEcho;
1316
1317 static void
1318 SaveInHistory (char *cmd)
1319 {
1320   if(noEcho) return; // do not save password!
1321   if (history[histIn] != NULL) {
1322     free(history[histIn]);
1323     history[histIn] = NULL;
1324   }
1325   if (*cmd == NULLCHAR) return;
1326   history[histIn] = StrSave(cmd);
1327   histIn = (histIn + 1) % HISTORY_SIZE;
1328   if (history[histIn] != NULL) {
1329     free(history[histIn]);
1330     history[histIn] = NULL;
1331   }
1332   histP = histIn;
1333 }
1334
1335 static char *
1336 PrevInHistory (char *cmd)
1337 {
1338   int newhp;
1339   if (histP == histIn) {
1340     if (history[histIn] != NULL) free(history[histIn]);
1341     history[histIn] = StrSave(cmd);
1342   }
1343   newhp = (histP - 1 + HISTORY_SIZE) % HISTORY_SIZE;
1344   if (newhp == histIn || history[newhp] == NULL) return NULL;
1345   histP = newhp;
1346   return history[histP];
1347 }
1348
1349 static char *
1350 NextInHistory ()
1351 {
1352   if (histP == histIn) return NULL;
1353   histP = (histP + 1) % HISTORY_SIZE;
1354   return history[histP];
1355 }
1356 // end of borrowed code
1357
1358 #define INPUT 0
1359
1360 Option boxOptions[] = {
1361 {  30, T_TOP, 400, NULL, (void*) &icsText, NULL, NULL, TextBox, "" },
1362 {  0,  NO_OK,   0, NULL, NULL, "", NULL, EndMark , "" }
1363 };
1364
1365 void
1366 ICSInputSendText ()
1367 {
1368     char *val;
1369
1370     GetWidgetText(&boxOptions[INPUT], &val);
1371     SaveInHistory(val);
1372     SendMultiLineToICS(val);
1373     SetWidgetText(&boxOptions[INPUT], "", InputBoxDlg);
1374 }
1375
1376 void
1377 IcsKey (int n)
1378 {   // [HGM] input: let up-arrow recall previous line from history
1379     char *val = NULL; // to suppress spurious warning
1380
1381     if (!shellUp[InputBoxDlg]) return;
1382     switch(n) {
1383       case 0:
1384         ICSInputSendText();
1385         return;
1386       case 1:
1387         GetWidgetText(&boxOptions[INPUT], &val);
1388         val = PrevInHistory(val);
1389         break;
1390       case -1:
1391         val = NextInHistory();
1392     }
1393     SetWidgetText(&boxOptions[INPUT], val = val ? val : "", InputBoxDlg);
1394     SetInsertPos(&boxOptions[INPUT], strlen(val));
1395 }
1396
1397 void
1398 ICSInputBoxPopUp ()
1399 {
1400     MarkMenu("View.ICSInputBox", InputBoxDlg);
1401     if(GenericPopUp(boxOptions, _("ICS input box"), InputBoxDlg, BoardWindow, NONMODAL, 0))
1402         AddHandler(&boxOptions[INPUT], InputBoxDlg, 3);
1403     CursorAtEnd(&boxOptions[INPUT]);
1404 }
1405
1406 void
1407 IcsInputBoxProc ()
1408 {
1409     if (!PopDown(InputBoxDlg)) ICSInputBoxPopUp();
1410 }
1411
1412 //--------------------------------------------- Move Type In ------------------------------------------
1413
1414 static int TypeInOK P((int n));
1415
1416 Option typeOptions[] = {
1417 { 30, T_TOP, 400, NULL, (void*) &icsText, NULL, NULL, TextBox, "" },
1418 { 0,  NO_OK,   0, NULL, (void*) &TypeInOK, "", NULL, EndMark , "" }
1419 };
1420
1421 static int
1422 TypeInOK (int n)
1423 {
1424     TypeInDoneEvent(icsText);
1425     return TRUE;
1426 }
1427
1428 void
1429 PopUpMoveDialog (char firstchar)
1430 {
1431     static char buf[2];
1432     buf[0] = firstchar; ASSIGN(icsText, buf);
1433     if(GenericPopUp(typeOptions, _("Type a move"), TransientDlg, BoardWindow, MODAL, 0))
1434         AddHandler(&typeOptions[0], TransientDlg, 2);
1435     CursorAtEnd(&typeOptions[0]);
1436 }
1437
1438 void
1439 BoxAutoPopUp (char *buf)
1440 {       // only used in Xaw. GTK calls ConsoleAutoPopUp in stead (when we type to board)
1441         if(!appData.autoBox) return;
1442         if(appData.icsActive) { // text typed to board in ICS mode: divert to ICS input box
1443             if(DialogExists(InputBoxDlg)) { // box already exists: append to current contents
1444                 char *p, newText[MSG_SIZ];
1445                 GetWidgetText(&boxOptions[INPUT], &p);
1446                 snprintf(newText, MSG_SIZ, "%s%c", p, *buf);
1447                 SetWidgetText(&boxOptions[INPUT], newText, InputBoxDlg);
1448                 if(shellUp[InputBoxDlg]) HardSetFocus (&boxOptions[INPUT], InputBoxDlg); //why???
1449             } else icsText = buf; // box did not exist: make sure it pops up with char in it
1450             ICSInputBoxPopUp();
1451         } else PopUpMoveDialog(*buf);
1452 }
1453
1454 //------------------------------------------ Engine Settings ------------------------------------
1455
1456 void
1457 SettingsPopUp (ChessProgramState *cps)
1458 {
1459    if(!cps->nrOptions) { DisplayNote(_("Engine has no options")); return; }
1460    currentCps = cps;
1461    GenericPopUp(cps->option, _("Engine Settings"), TransientDlg, BoardWindow, MODAL, 0);
1462 }
1463
1464 void
1465 FirstSettingsProc ()
1466 {
1467     SettingsPopUp(&first);
1468 }
1469
1470 void
1471 SecondSettingsProc ()
1472 {
1473    if(WaitForEngine(&second, SettingsMenuIfReady)) return;
1474    SettingsPopUp(&second);
1475 }
1476
1477 void
1478 RefreshSettingsDialog (ChessProgramState *cps, int val)
1479 {
1480    if(val == 1) { // option values changed
1481       if(shellUp[TransientDlg] && cps == currentCps) {
1482          GenericUpdate(cps->option, -1); // normally update values when dialog is up
1483       }
1484       return; // and be done
1485    }
1486    if(val == 2) { // option list changed
1487       if(!shellUp[TransientDlg] || cps != currentCps) return; // our dialog is not up, so nothing to do
1488    }
1489    PopDown(TransientDlg); // make sure any other dialog closes first
1490    SettingsPopUp(cps);    // and popup new one
1491 }
1492
1493 //----------------------------------------------- Load Engine --------------------------------------
1494
1495 char *engineDir, *engineLine, *nickName, *params;
1496 Boolean isUCI, isUSI, hasBook, storeVariant, v1, addToList, useNick, secondEng;
1497
1498 static void EngSel P((int n, int sel));
1499 static int InstallOK P((int n));
1500
1501 static Option installOptions[] = {
1502 {   0,LR|T2T, 0, NULL, NULL, NULL, NULL, Label, N_("Select engine from list:") },
1503 { 300,LR|TB,200, NULL, (void*) engineMnemonic, (char*) &EngSel, NULL, ListBox, "" },
1504 { 0,SAME_ROW, 0, NULL, NULL, NULL, NULL, Break, NULL },
1505 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("or specify one below:") },
1506 {   0,  0,    0, NULL, (void*) &nickName, NULL, NULL, TextBox, N_("Nickname (optional):") },
1507 {   0,  0,    0, NULL, (void*) &useNick, NULL, NULL, CheckBox, N_("Use nickname in PGN player tags of engine-engine games") },
1508 {   0,  0,    0, NULL, (void*) &engineDir, NULL, NULL, PathName, N_("Engine Directory:") },
1509 {   0,  0,    0, NULL, (void*) &engineName, NULL, NULL, FileName, N_("Engine Command:") },
1510 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("(Directory will be derived from engine path when empty)") },
1511 {   0,  0,    0, NULL, (void*) &isUCI, NULL, NULL, CheckBox, N_("UCI") },
1512 {   0,  0,    0, NULL, (void*) &isUSI, NULL, NULL, CheckBox, N_("USI/UCCI (uses specified -uxiAdapter)") },
1513 {   0,  0,    0, NULL, (void*) &v1, NULL, NULL, CheckBox, N_("WB protocol v1 (do not wait for engine features)") },
1514 {   0,  0,    0, NULL, (void*) &hasBook, NULL, NULL, CheckBox, N_("Must not use GUI book") },
1515 {   0,  0,    0, NULL, (void*) &addToList, NULL, NULL, CheckBox, N_("Add this engine to the list") },
1516 {   0,  0,    0, NULL, (void*) &storeVariant, NULL, NULL, CheckBox, N_("Force current variant with this engine") },
1517 {   0,  0,    0, NULL, (void*) &InstallOK, "", NULL, EndMark , "" }
1518 };
1519
1520 static int
1521 InstallOK (int n)
1522 {
1523     if(n && (n = SelectedListBoxItem(&installOptions[1])) > 0) { // called by pressing OK, and engine selected
1524         ASSIGN(engineLine, engineList[n]);
1525     }
1526     PopDown(TransientDlg); // early popdown, to allow FreezeUI to instate grab
1527     if(isUSI) {
1528         isUCI = 2; // kludge to pass isUSI to Load()
1529         if(!*appData.ucciAdapter) { ASSIGN(appData.ucciAdapter, "usi2wb -%variant \"%fcp\"\"%fd\""); } // make sure -uxiAdapter is defined
1530     }
1531     if(!secondEng) Load(&first, 0); else Load(&second, 1);
1532     return FALSE; // no double PopDown!
1533 }
1534
1535 static void
1536 EngSel (int n, int sel)
1537 {
1538     int nr;
1539     char buf[MSG_SIZ];
1540     if(sel < 1) buf[0] = NULLCHAR; // back to top level
1541     else if(engineList[sel][0] == '#') safeStrCpy(buf, engineList[sel], MSG_SIZ); // group header, open group
1542     else { // normal line, select engine
1543         ASSIGN(engineLine, engineList[sel]);
1544         InstallOK(0);
1545         return;
1546     }
1547     nr = NamesToList(firstChessProgramNames, engineList, engineMnemonic, buf); // replace list by only the group contents
1548     ASSIGN(engineMnemonic[0], buf);
1549     LoadListBox(&installOptions[1], _("# no engines are installed"), -1, -1);
1550     HighlightWithScroll(&installOptions[1], 0, nr);
1551 }
1552
1553 static void
1554 LoadEngineProc (int engineNr, char *title)
1555 {
1556    isUCI = isUSI = storeVariant = v1 = useNick = False; addToList = hasBook = True; // defaults
1557    secondEng = engineNr;
1558    if(engineLine)   free(engineLine);   engineLine = strdup("");
1559    if(engineDir)    free(engineDir);    engineDir = strdup(".");
1560    if(nickName)     free(nickName);     nickName = strdup("");
1561    if(params)       free(params);       params = strdup("");
1562    ASSIGN(engineMnemonic[0], "");
1563    NamesToList(firstChessProgramNames, engineList, engineMnemonic, "");
1564    GenericPopUp(installOptions, title, TransientDlg, BoardWindow, MODAL, 0);
1565 }
1566
1567 void
1568 LoadEngine1Proc ()
1569 {
1570     LoadEngineProc (0, _("Load first engine"));
1571 }
1572
1573 void
1574 LoadEngine2Proc ()
1575 {
1576     LoadEngineProc (1, _("Load second engine"));
1577 }
1578
1579 //----------------------------------------------------- Edit Book -----------------------------------------
1580
1581 void
1582 EditBookProc ()
1583 {
1584     EditBookEvent();
1585 }
1586
1587 //--------------------------------------------------- New Shuffle Game ------------------------------
1588
1589 static void SetRandom P((int n));
1590
1591 static int
1592 ShuffleOK (int n)
1593 {
1594     ResetGameEvent();
1595     return 1;
1596 }
1597
1598 static Option shuffleOptions[] = {
1599   {   0,  0,    0, NULL, (void*) &shuffleOpenings, NULL, NULL, CheckBox, N_("shuffle") },
1600   {   0,  0,    0, NULL, (void*) &appData.fischerCastling, NULL, NULL, CheckBox, N_("Fischer castling") },
1601   { 0,-1,2000000000, NULL, (void*) &appData.defaultFrcPosition, "", NULL, Spin, N_("Start-position number:") },
1602   {   0,  0,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("randomize") },
1603   {   0,  SAME_ROW,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("pick fixed") },
1604   { 0,SAME_ROW, 0, NULL, (void*) &ShuffleOK, "", NULL, EndMark , "" }
1605 };
1606
1607 static void
1608 SetRandom (int n)
1609 {
1610     int r = n==3 ? -1 : random() & (1<<30)-1;
1611     char buf[MSG_SIZ];
1612     snprintf(buf, MSG_SIZ,  "%d", r);
1613     SetWidgetText(&shuffleOptions[2], buf, TransientDlg);
1614     SetWidgetState(&shuffleOptions[0], True);
1615 }
1616
1617 void
1618 ShuffleMenuProc ()
1619 {
1620     GenericPopUp(shuffleOptions, _("New Shuffle Game"), TransientDlg, BoardWindow, MODAL, 0);
1621 }
1622
1623 //--------------------------------------------------- Fonts ------------------------------
1624
1625 static void AdjustFont P((int n));
1626
1627 static int
1628 FontsOK (int n)
1629 {
1630     extern Option historyOptions[], engoutOptions[], gamesOptions[];
1631     int i;
1632     ApplyFont(&mainOptions[W_WHITE], appData.clockFont);
1633     ApplyFont(&mainOptions[W_BLACK], appData.clockFont);
1634     ApplyFont(&mainOptions[W_MESSG], NULL);
1635     for(i=1; i<6; i++) ApplyFont(&mainOptions[W_BUTTON+i], NULL);
1636     ApplyFont(&tagsOptions[1], NULL);
1637     ApplyFont(&historyOptions[0], NULL);
1638     ApplyFont(&engoutOptions[5], NULL);
1639     ApplyFont(&engoutOptions[12], NULL);
1640     ApplyFont(&gamesOptions[0], NULL);
1641     DrawPosition(TRUE, NULL); // for coord font
1642     return 1;
1643 }
1644
1645 static Option fontOptions[] = {
1646   { 0,          0, 70, NULL, (void*) &appData.clockFont, "", NULL, TextBox, N_("Clocks:") },
1647   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1648   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1649   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1650   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1651   { 0,          0, 70, NULL, (void*) &appData.font, "", NULL, TextBox, N_("Message (above board):") },
1652   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1653   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1654   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1655   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1656   { 0,          0, 70, NULL, (void*) &appData.coordFont, "", NULL, TextBox, N_("Board coordinates:") },
1657   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1658   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1659   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1660   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1661   { 0,          0, 70, NULL, (void*) &appData.tagsFont, "", NULL, TextBox, N_("Edit tags / book / engine list:") },
1662   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1663   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1664   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1665   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1666   { 0,          0, 70, NULL, (void*) &appData.commentFont, "", NULL, TextBox, N_("Edit comments:") },
1667   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1668   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1669   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1670   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1671   { 0,          0, 70, NULL, (void*) &appData.historyFont, "", NULL, TextBox, N_("Move history / Engine Output:") },
1672   {    1, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("+") },
1673   {    2, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("-") },
1674   {    3, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("B") },
1675   {    4, SAME_ROW, 0, NULL, (void*) &AdjustFont, NULL, NULL, Button, N_("I") },
1676   { 0,          0, 70, NULL, (void*) &appData.gameListFont, "", NULL, TextBox, N_("Game list:") },
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   {   0,  0,    0, NULL, NULL, NULL, NULL, Label, N_("Generic type-face names are Sans, Monospace and Serif") },
1682   { 0, 0, 0, NULL, (void*) &FontsOK, "", NULL, EndMark , "" }
1683 };
1684
1685 static char name[MSG_SIZ], *bold, *ital, points;
1686
1687 static void
1688 BreakUp (char *font)
1689 {
1690     char *p = name;
1691     safeStrCpy(name, font, MSG_SIZ);
1692     bold = StrCaseStr(name, "bold");
1693     ital = StrCaseStr(name, "ital");
1694     points = 0;
1695     while(p && *p && !(points = atoi(p))) p = strchr(p+1, ' ');
1696     if(points) p[*p == ' '] = 0;
1697     if(bold) *bold = 0;
1698     if(ital) *ital = 0;
1699 }
1700
1701 static void
1702 Collect ()
1703 {
1704     if(bold) strcat(name, "Bold ");
1705     if(ital) strcat(name, "Italic ");
1706     if(points) sprintf(name + strlen(name), " %d", points); else strcat(name, "%d");
1707 }
1708
1709 static void
1710 AdjustFont (int n)
1711 {
1712     int button = fontOptions[n].value, base = n - button;
1713     char *oldFont;
1714     GetWidgetText(&fontOptions[base], &oldFont);
1715     BreakUp(oldFont); // take apart old font name
1716     switch(button) {
1717       case 1: points++; break;
1718       case 2: points--; break;
1719       case 3: if(bold) bold = NULL; else bold = name; break;
1720       case 4: if(ital) ital = NULL; else ital = name; break;
1721     }
1722     Collect();
1723     SetWidgetText(&fontOptions[base], name, TransientDlg);
1724 //    ApplyFont(&fontOptions[base], name);
1725 }
1726
1727 void
1728 FontsProc ()
1729 {
1730     int i;
1731     GenericPopUp(fontOptions, _("Fonts"), TransientDlg, BoardWindow, MODAL, 0);
1732 //    for(i=0; i<6; i++) ApplyFont(&fontOptions[5*i], *(char**)fontOptions[5*i].target);
1733 }
1734
1735 //------------------------------------------------------ Time Control -----------------------------------
1736
1737 static int TcOK P((int n));
1738 int tmpMoves, tmpTc, tmpInc, tmpOdds1, tmpOdds2, tcType, by60;
1739
1740 static void SetTcType P((int n));
1741
1742 static char *
1743 Value (int n)
1744 {
1745         static char buf[MSG_SIZ];
1746         snprintf(buf, MSG_SIZ, "%d", n);
1747         return buf;
1748 }
1749
1750 static Option tcOptions[] = {
1751 {   0,  0,    0, NULL, (void*) &SetTcType, NULL, NULL, Button, N_("classical") },
1752 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("incremental") },
1753 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("fixed max") },
1754 {   0,  0,    0, NULL, (void*) &by60,     "",  NULL, CheckBox, N_("Divide entered times by 60") },
1755 {   0,  0,  200, NULL, (void*) &tmpMoves, NULL, NULL, Spin, N_("Moves per session:") },
1756 {   0,  0,10000, NULL, (void*) &tmpTc,    NULL, NULL, Spin, N_("Initial time (min):") },
1757 {   0, 0, 10000, NULL, (void*) &tmpInc,   NULL, NULL, Spin, N_("Increment or max (sec/move):") },
1758 {   0,  0,    0, NULL, NULL, NULL, NULL, Label, N_("Time-Odds factors:") },
1759 {   0,  1, 1000, NULL, (void*) &tmpOdds1, NULL, NULL, Spin, N_("Engine #1") },
1760 {   0,  1, 1000, NULL, (void*) &tmpOdds2, NULL, NULL, Spin, N_("Engine #2 / Human") },
1761 {   0,  0,    0, NULL, (void*) &TcOK, "", NULL, EndMark , "" }
1762 };
1763
1764 static int
1765 TcOK (int n)
1766 {
1767     char *tc, buf[MSG_SIZ];
1768     if(tcType == 0 && tmpMoves <= 0) return 0;
1769     if(tcType == 2 && tmpInc <= 0) return 0;
1770     GetWidgetText(&tcOptions[5], &tc); // get original text, in case it is min:sec
1771     if(by60) snprintf(buf, MSG_SIZ, "%d:%02d", tmpTc/60, tmpTc%60), tc=buf;
1772     searchTime = 0;
1773     switch(tcType) {
1774       case 0:
1775         if(!ParseTimeControl(tc, -1, tmpMoves)) return 0;
1776         appData.movesPerSession = tmpMoves;
1777         ASSIGN(appData.timeControl, tc);
1778         appData.timeIncrement = -1;
1779         break;
1780       case 1:
1781         if(!ParseTimeControl(tc, tmpInc, 0)) return 0;
1782         ASSIGN(appData.timeControl, tc);
1783         appData.timeIncrement = (by60 ? tmpInc/60. : tmpInc);
1784         break;
1785       case 2:
1786         searchTime = (by60 ? tmpInc/60 : tmpInc);
1787     }
1788     appData.firstTimeOdds = first.timeOdds = tmpOdds1;
1789     appData.secondTimeOdds = second.timeOdds = tmpOdds2;
1790     Reset(True, True);
1791     return 1;
1792 }
1793
1794 static void
1795 SetTcType (int n)
1796 {
1797     switch(tcType = n) {
1798       case 0:
1799         SetWidgetText(&tcOptions[4], Value(tmpMoves), TransientDlg);
1800         SetWidgetText(&tcOptions[5], Value(tmpTc), TransientDlg);
1801         SetWidgetText(&tcOptions[6], _("Unused"), TransientDlg);
1802         break;
1803       case 1:
1804         SetWidgetText(&tcOptions[4], _("Unused"), TransientDlg);
1805         SetWidgetText(&tcOptions[5], Value(tmpTc), TransientDlg);
1806         SetWidgetText(&tcOptions[6], Value(tmpInc), TransientDlg);
1807         break;
1808       case 2:
1809         SetWidgetText(&tcOptions[4], _("Unused"), TransientDlg);
1810         SetWidgetText(&tcOptions[5], _("Unused"), TransientDlg);
1811         SetWidgetText(&tcOptions[6], Value(tmpInc), TransientDlg);
1812     }
1813 }
1814
1815 void
1816 TimeControlProc ()
1817 {
1818    if(gameMode != BeginningOfGame) {
1819         DisplayError(_("Changing time control during a game is not implemented"), 0);
1820         return;
1821    }
1822    tmpMoves = appData.movesPerSession;
1823    tmpInc = appData.timeIncrement; if(tmpInc < 0) tmpInc = 0;
1824    tmpOdds1 = tmpOdds2 = 1; tcType = 0;
1825    tmpTc = atoi(appData.timeControl);
1826    by60 = 0;
1827    GenericPopUp(tcOptions, _("Time Control"), TransientDlg, BoardWindow, MODAL, 0);
1828    SetTcType(searchTime ? 2 : appData.timeIncrement < 0 ? 0 : 1);
1829 }
1830
1831 //------------------------------- Ask Question -----------------------------------------
1832
1833 int SendReply P((int n));
1834 char pendingReplyPrefix[MSG_SIZ];
1835 ProcRef pendingReplyPR;
1836 char *answer;
1837
1838 Option askOptions[] = {
1839 { 0, 0, 0, NULL, NULL, NULL, NULL, Label,  NULL },
1840 { 0, 0, 0, NULL, (void*) &answer, "", NULL, TextBox, "" },
1841 { 0, 0, 0, NULL, (void*) &SendReply, "", NULL, EndMark , "" }
1842 };
1843
1844 int
1845 SendReply (int n)
1846 {
1847     char buf[MSG_SIZ];
1848     int err;
1849     char *reply=answer;
1850 //    GetWidgetText(&askOptions[1], &reply);
1851     safeStrCpy(buf, pendingReplyPrefix, sizeof(buf)/sizeof(buf[0]) );
1852     if (*buf) strncat(buf, " ", MSG_SIZ - strlen(buf) - 1);
1853     strncat(buf, reply, MSG_SIZ - strlen(buf) - 1);
1854     strncat(buf, "\n",  MSG_SIZ - strlen(buf) - 1);
1855     OutputToProcess(pendingReplyPR, buf, strlen(buf), &err); // does not go into debug file??? => bug
1856     if (err) DisplayFatalError(_("Error writing to chess program"), err, 0);
1857     return TRUE;
1858 }
1859
1860 void
1861 AskQuestion (char *title, char *question, char *replyPrefix, ProcRef pr)
1862 {
1863     safeStrCpy(pendingReplyPrefix, replyPrefix, sizeof(pendingReplyPrefix)/sizeof(pendingReplyPrefix[0]) );
1864     pendingReplyPR = pr;
1865     ASSIGN(answer, "");
1866     askOptions[0].name = question;
1867     if(GenericPopUp(askOptions, title, AskDlg, BoardWindow, MODAL, 0))
1868         AddHandler(&askOptions[1], AskDlg, 2);
1869 }
1870
1871 //---------------------------- Promotion Popup --------------------------------------
1872
1873 static int count;
1874
1875 static void PromoPick P((int n));
1876
1877 static Option promoOptions[] = {
1878 {   0,         0,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1879 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1880 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1881 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1882 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1883 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1884 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1885 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1886 {   0, SAME_ROW | NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
1887 };
1888
1889 static void
1890 PromoPick (int n)
1891 {
1892     int promoChar = promoOptions[n+count].value;
1893
1894     PopDown(PromoDlg);
1895
1896     if (promoChar == 0) fromX = -1;
1897     if (fromX == -1) return;
1898
1899     if (! promoChar) {
1900         fromX = fromY = -1;
1901         ClearHighlights();
1902         return;
1903     }
1904     if(promoChar == '=' && !IS_SHOGI(gameInfo.variant)) promoChar = NULLCHAR;
1905     UserMoveEvent(fromX, fromY, toX, toY, promoChar);
1906
1907     if (!appData.highlightLastMove || gotPremove) ClearHighlights();
1908     if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
1909     fromX = fromY = -1;
1910 }
1911
1912 static void
1913 SetPromo (char *name, int nr, char promoChar)
1914 {
1915     ASSIGN(promoOptions[nr].name, name);
1916     promoOptions[nr].value = promoChar;
1917     promoOptions[nr].min = SAME_ROW;
1918 }
1919
1920 void
1921 PromotionPopUp (char choice)
1922 { // choice depends on variant: prepare dialog acordingly
1923   count = 8;
1924   SetPromo(_("Cancel"), --count, -1); // Beware: GenericPopUp cannot handle user buttons named "cancel" (lowe case)!
1925   if(choice != '+' && !IS_SHOGI(gameInfo.variant)) {
1926     if (!appData.testLegality || gameInfo.variant == VariantSuicide ||
1927         gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove) ||
1928         gameInfo.variant == VariantGiveaway) {
1929       SetPromo(_("King"), --count, 'k');
1930     }
1931     if(gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove)) {
1932       SetPromo(_("Captain"), --count, 'c');
1933       SetPromo(_("Lieutenant"), --count, 'l');
1934       SetPromo(_("General"), --count, 'g');
1935       SetPromo(_("Warlord"), --count, 'w');
1936     } else {
1937       SetPromo(_("Knight"), --count, 'n');
1938       SetPromo(_("Bishop"), --count, 'b');
1939       SetPromo(_("Rook"), --count, 'r');
1940       if(gameInfo.variant == VariantCapablanca ||
1941          gameInfo.variant == VariantGothic ||
1942          gameInfo.variant == VariantCapaRandom) {
1943         SetPromo(_("Archbishop"), --count, 'a');
1944         SetPromo(_("Chancellor"), --count, 'c');
1945       }
1946       SetPromo(_("Queen"), --count, 'q');
1947       if(gameInfo.variant == VariantChuChess)
1948         SetPromo(_("Lion"), --count, 'l');
1949     }
1950   } else // [HGM] shogi
1951   {
1952       SetPromo(_("Defer"), --count, '=');
1953       SetPromo(_("Promote"), --count, '+');
1954   }
1955   promoOptions[count].min = 0;
1956   GenericPopUp(promoOptions + count, "Promotion", PromoDlg, BoardWindow, NONMODAL, 0);
1957 }
1958
1959 //---------------------------- Chat Windows ----------------------------------------------
1960
1961 static char *line, *memo, *chatMemo, *partner, *texts[MAX_CHAT], dirty[MAX_CHAT], *inputs[MAX_CHAT], *icsLine, *tmpLine;
1962 static int activePartner;
1963 int hidden = 1;
1964
1965 void ChatSwitch P((int n));
1966 int  ChatOK P((int n));
1967
1968 #define CHAT_ICS     6
1969 #define CHAT_PARTNER 8
1970 #define CHAT_OUT    11
1971 #define CHAT_PANE   12
1972 #define CHAT_IN     13
1973
1974 void PaneSwitch P((void));
1975 void ClearChat P((void));
1976
1977 WindowPlacement wpTextMenu;
1978
1979 int
1980 ContextMenu (Option *opt, int button, int x, int y, char *text, int index)
1981 { // callback for ICS-output clicks; handles button 3, passes on other events
1982   int h;
1983   if(button == -3) return TRUE; // supress default GTK context menu on up-click
1984   if(button != 3) return FALSE;
1985   if(index == -1) { // pre-existing selection in memo
1986     strncpy(clickedWord, text, MSG_SIZ);
1987   } else { // figure out what word was clicked
1988     char *start, *end;
1989     start = end = text + index;
1990     while(isalnum(*end)) end++;
1991     while(start > text && isalnum(start[-1])) start--;
1992     clickedWord[0] = NULLCHAR;
1993     if(end-start >= 80) end = start + 80; // intended for small words and numbers
1994     strncpy(clickedWord, start, end-start); clickedWord[end-start] = NULLCHAR;
1995   }
1996   click = !shellUp[TextMenuDlg]; // request auto-popdown of textmenu when we popped it up
1997   h = wpTextMenu.height; // remembered height of text menu
1998   if(h <= 0) h = 65;     // when not available, position w.r.t. top
1999   GetPlacement(ChatDlg, &wpTextMenu);
2000   if(opt->target == (void*) &chatMemo) wpTextMenu.y += (wpTextMenu.height - 30)/2; // click in chat
2001   wpTextMenu.x += x - 50; wpTextMenu.y += y - h + 50; // request positioning
2002   if(wpTextMenu.x < 0) wpTextMenu.x = 0;
2003   if(wpTextMenu.y < 0) wpTextMenu.y = 0;
2004   wpTextMenu.width = wpTextMenu.height = -1;
2005   IcsTextPopUp();
2006   return TRUE;
2007 }
2008
2009 Option chatOptions[] = {
2010 {  0,  0,   0, NULL, NULL, NULL, NULL, Label , N_("Chats:") },
2011 { 1, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
2012 { 2, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
2013 { 3, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
2014 { 4, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
2015 { 5, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
2016 { 250, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &memo, NULL, (void*) &ContextMenu, TextBox, "" },
2017 {  0,  0,   0, NULL, NULL, "", NULL, Break , "" },
2018 { 0,   T_TOP,    100, NULL, (void*) &partner, NULL, NULL, TextBox, N_("Chat partner:") },
2019 {  0, SAME_ROW, 0, NULL, (void*) &ClearChat,  NULL, NULL, Button, N_("End Chat") },
2020 {  0, SAME_ROW, 0, NULL, (void*) &PaneSwitch, NULL, NULL, Button, N_("Hide") },
2021 { 250, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &chatMemo, NULL, (void*) &ContextMenu, TextBox, "" },
2022 {  0,  0,   0, NULL, NULL, "", NULL, Break , "" },
2023 {  0,    0,  510, NULL, (void*) &line, NULL, NULL, TextBox, "" },
2024 { 0, NO_OK|SAME_ROW, 0, NULL, (void*) &ChatOK, NULL, NULL, EndMark , "" }
2025 };
2026
2027 static void
2028 PutText (char *text, int pos)
2029 {
2030     char buf[MSG_SIZ], *p;
2031     DialogClass dlg = ChatDlg;
2032     Option *opt = &chatOptions[CHAT_IN];
2033
2034     if(strstr(text, "$add ") == text) {
2035         GetWidgetText(&boxOptions[INPUT], &p);
2036         snprintf(buf, MSG_SIZ, "%s%s", p, text+5); text = buf;
2037         pos += strlen(p) - 5;
2038     }
2039     if(shellUp[InputBoxDlg]) opt = &boxOptions[INPUT], dlg = InputBoxDlg; // for the benefit of Xaw give priority to ICS Input Box
2040     SetWidgetText(opt, text, dlg);
2041     SetInsertPos(opt, pos);
2042     HardSetFocus(opt, dlg);
2043     CursorAtEnd(opt);
2044 }
2045
2046 int
2047 IcsHist (int n, Option *opt, DialogClass dlg)
2048 {   // [HGM] input: let up-arrow recall previous line from history
2049     char *val = NULL; // to suppress spurious warning
2050     int chat, start;
2051
2052     if(opt != &chatOptions[CHAT_IN] && !(opt == &chatOptions[CHAT_PARTNER] && n == 33)) return 0;
2053     switch(n) {
2054       case 5:
2055         if(!hidden) ClearChat();
2056         break;
2057       case 8:
2058         if(!hidden) PaneSwitch();
2059         break;
2060       case 33: // <Esc>
2061         if(1) BoardToTop(); else
2062         if(hidden) BoardToTop();
2063         else PaneSwitch();
2064         break;
2065       case 15:
2066         NewChat(lastTalker);
2067         break;
2068       case 14:
2069         for(chat=0; chat < MAX_CHAT; chat++) if(!chatPartner[chat][0]) break;
2070         if(chat < MAX_CHAT) ChatSwitch(chat + 1);
2071         break;
2072       case 10: // <Tab>
2073         chat = start = (activePartner - hidden + MAX_CHAT) % MAX_CHAT;
2074         while(!dirty[chat = (chat + 1)%MAX_CHAT]) if(chat == start) break;
2075         if(!dirty[chat])
2076         while(!chatPartner[chat = (chat + 1)%MAX_CHAT][0]) if(chat == start) break;
2077         if(!chatPartner[chat][0]) break; // if all unused, ignore
2078         ChatSwitch(chat + 1);
2079         break;
2080       case 1:
2081         GetWidgetText(opt, &val);
2082         val = PrevInHistory(val);
2083         break;
2084       case -1:
2085         val = NextInHistory();
2086     }
2087     SetWidgetText(opt, val = val ? val : "", dlg);
2088     SetInsertPos(opt, strlen(val));
2089     return 1;
2090 }
2091
2092 void
2093 OutputChatMessage (int partner, char *mess)
2094 {
2095     char *p = texts[partner];
2096     int len = strlen(mess) + 1;
2097
2098     if(!DialogExists(ChatDlg)) return;
2099     if(p) len += strlen(p);
2100     texts[partner] = (char*) malloc(len);
2101     snprintf(texts[partner], len, "%s%s", p ? p : "", mess);
2102     FREE(p);
2103     if(partner == activePartner && !hidden) {
2104         AppendText(&chatOptions[CHAT_OUT], mess);
2105         SetInsertPos(&chatOptions[CHAT_OUT], len-2);
2106     } else {
2107         SetColor("#FFC000", &chatOptions[partner + 1]);
2108         dirty[partner] = 1;
2109     }
2110 }
2111
2112 int
2113 ChatOK (int n)
2114 {   // can only be called through <Enter> in chat-partner text-edit, as there is no OK button
2115     char buf[MSG_SIZ];
2116
2117     if(!hidden && (!partner || strcmp(partner, chatPartner[activePartner]) || !*partner)) {
2118         safeStrCpy(chatPartner[activePartner], partner, MSG_SIZ);
2119         SetWidgetText(&chatOptions[CHAT_OUT], "", -1); // clear text if we alter partner
2120         SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg); // clear text if we alter partner
2121         SetWidgetLabel(&chatOptions[activePartner+1], chatPartner[activePartner][0] ? chatPartner[activePartner] : _("New Chat"));
2122         if(!*partner) PaneSwitch();
2123         HardSetFocus(&chatOptions[CHAT_IN], 0);
2124     }
2125     if(line[0] || hidden) { // something was typed (for ICS commands we also allow empty line!)
2126         SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg);
2127         // from here on it could be back-end
2128         if(line[strlen(line)-1] == '\n') line[strlen(line)-1] = NULLCHAR;
2129         SaveInHistory(line);
2130         if(hidden || !*chatPartner[activePartner]) snprintf(buf, MSG_SIZ, "%s\n", line); else // command for ICS
2131         if(!strcmp("whispers", chatPartner[activePartner]))
2132               snprintf(buf, MSG_SIZ, "whisper %s\n", line); // WHISPER box uses "whisper" to send
2133         else if(!strcmp("shouts", chatPartner[activePartner]))
2134               snprintf(buf, MSG_SIZ, "shout %s\n", line); // SHOUT box uses "shout" to send
2135         else if(!strcmp("c-shouts", chatPartner[activePartner]))
2136               snprintf(buf, MSG_SIZ, "cshout %s\n", line); // C-SHOUT box uses "cshout" to send
2137         else if(!strcmp("kibitzes", chatPartner[activePartner]))
2138               snprintf(buf, MSG_SIZ, "kibitz %s\n", line); // KIBITZ box uses "kibitz" to send
2139         else {
2140             if(!atoi(chatPartner[activePartner])) {
2141                 snprintf(buf, MSG_SIZ, "> %s\n", line); // echo only tells to handle, not channel
2142                 OutputChatMessage(activePartner, buf);
2143                 snprintf(buf, MSG_SIZ, "xtell %s %s\n", chatPartner[activePartner], line);
2144             } else
2145                 snprintf(buf, MSG_SIZ, "tell %s %s\n", chatPartner[activePartner], line);
2146         }
2147         SendToICS(buf);
2148     }
2149     return FALSE; // never pop down
2150 }
2151
2152 void
2153 DelayedSetText ()
2154 {
2155     SetWidgetText(&chatOptions[CHAT_IN], tmpLine, -1); // leave focus on chat-partner field!
2156     SetInsertPos(&chatOptions[CHAT_IN], strlen(tmpLine));
2157 }
2158
2159 void
2160 DelayedScroll ()
2161 {   // If we do this immediately it does it before shrinking the memo, so the lower half remains hidden (Ughh!)
2162     SetInsertPos(&chatOptions[CHAT_ICS], 999999);
2163     SetWidgetText(&chatOptions[CHAT_IN], tmpLine, ChatDlg);
2164     SetInsertPos(&chatOptions[CHAT_IN], strlen(tmpLine));
2165 }
2166
2167 void
2168 ChatSwitch (int n)
2169 {
2170     int i, j;
2171     char *v;
2172     if(chatOptions[CHAT_ICS].type == Skip) hidden = 0; // In Xaw there is no ICS pane we can hide behind
2173     Show(&chatOptions[CHAT_PANE], 0); // show
2174     if(hidden) ScheduleDelayedEvent(DelayedScroll, 50); // Awful!
2175     else ScheduleDelayedEvent(DelayedSetText, 50);
2176     GetWidgetText(&chatOptions[CHAT_IN], &v);
2177     if(hidden) { ASSIGN(icsLine, v); } else { ASSIGN(inputs[activePartner], v); }
2178     hidden = 0;
2179     activePartner = --n;
2180     if(!texts[n]) texts[n] = strdup("");
2181     dirty[n] = 0;
2182     SetWidgetText(&chatOptions[CHAT_OUT], texts[n], ChatDlg);
2183     SetInsertPos(&chatOptions[CHAT_OUT], strlen(texts[n]));
2184     SetWidgetText(&chatOptions[CHAT_PARTNER], chatPartner[n], ChatDlg);
2185     for(i=j=0; i<MAX_CHAT; i++) {
2186         SetWidgetLabel(&chatOptions[++j], *chatPartner[i] ? chatPartner[i] : _("New Chat"));
2187         SetColor(dirty[i] ? "#FFC000" : "#FFFFFF", &chatOptions[j]);
2188     }
2189     if(!inputs[n]) { ASSIGN(inputs[n], ""); }
2190 //    SetWidgetText(&chatOptions[CHAT_IN], inputs[n], ChatDlg); // does not work (in this widget only)
2191 //    SetInsertPos(&chatOptions[CHAT_IN], strlen(inputs[n]));
2192     tmpLine = inputs[n]; // for the delayed event
2193     HardSetFocus(&chatOptions[strcmp(chatPartner[n], "") ? CHAT_IN : CHAT_PARTNER], 0);
2194 }
2195
2196 void
2197 PaneSwitch ()
2198 {
2199     char *v;
2200     Show(&chatOptions[CHAT_PANE], hidden = 1); // hide
2201     GetWidgetText(&chatOptions[CHAT_IN], &v);
2202     ASSIGN(inputs[activePartner], v);
2203     if(!icsLine) { ASSIGN(icsLine, ""); }
2204     tmpLine = icsLine; ScheduleDelayedEvent(DelayedSetText, 50);
2205 //    SetWidgetText(&chatOptions[CHAT_IN], icsLine, ChatDlg); // does not work (in this widget only)
2206 //    SetInsertPos(&chatOptions[CHAT_IN], strlen(icsLine));
2207 }
2208
2209 void
2210 ClearChat ()
2211 {   // clear the chat to make it free for other use
2212     chatPartner[activePartner][0] = NULLCHAR;
2213     ASSIGN(texts[activePartner], "");
2214     ASSIGN(inputs[activePartner], "");
2215     SetWidgetText(&chatOptions[CHAT_PARTNER], "", ChatDlg);
2216     SetWidgetText(&chatOptions[CHAT_OUT], "", ChatDlg);
2217     SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg);
2218     SetWidgetLabel(&chatOptions[activePartner+1], _("New Chat"));
2219     HardSetFocus(&chatOptions[CHAT_PARTNER], 0);
2220 }
2221
2222 static void
2223 NewChat (char *name)
2224 {   // open a chat on program request. If no empty one available, use last
2225     int i;
2226     for(i=0; i<MAX_CHAT-1; i++) if(!chatPartner[i][0]) break;
2227     safeStrCpy(chatPartner[i], name, MSG_SIZ);
2228     ChatSwitch(i+1);
2229 }
2230
2231 void
2232 ConsoleWrite(char *message, int count)
2233 {
2234     if(shellUp[ChatDlg] && chatOptions[CHAT_ICS].type != Skip) { // in Xaw this is a no-op
2235         if(*message == 7) {
2236             message++; // remove bell
2237             if(strcmp(message, "\n")) return;
2238         }
2239         AppendColorized(&chatOptions[CHAT_ICS], message, count);
2240         SetInsertPos(&chatOptions[CHAT_ICS], 999999);
2241     }
2242 }
2243
2244 void
2245 ChatPopUp ()
2246 {
2247     if(GenericPopUp(chatOptions, _("ICS Interaction"), ChatDlg, BoardWindow, NONMODAL, appData.topLevel))
2248         AddHandler(&chatOptions[CHAT_PARTNER], ChatDlg, 2), AddHandler(&chatOptions[CHAT_IN], ChatDlg, 2); // treats return as OK
2249     Show(&chatOptions[CHAT_PANE], hidden = 1); // hide
2250 //    HardSetFocus(&chatOptions[CHAT_IN], 0);
2251     MarkMenu("View.OpenChatWindow", ChatDlg);
2252     CursorAtEnd(&chatOptions[CHAT_IN]);
2253 }
2254
2255 void
2256 ChatProc ()
2257 {
2258     if(shellUp[ChatDlg]) PopDown(ChatDlg);
2259     else ChatPopUp();
2260 }
2261
2262 void
2263 ConsoleAutoPopUp (char *buf)
2264 {
2265         if(*buf == 27) { if(appData.icsActive && DialogExists(ChatDlg)) HardSetFocus (&chatOptions[CHAT_IN], ChatDlg); return; }
2266         if(!appData.autoBox) return;
2267         if(appData.icsActive) { // text typed to board in ICS mode: divert to ICS input box
2268             if(DialogExists(ChatDlg)) { // box already exists: append to current contents
2269                 char *p, newText[MSG_SIZ];
2270                 GetWidgetText(&chatOptions[CHAT_IN], &p);
2271                 snprintf(newText, MSG_SIZ, "%s%c", p, *buf);
2272                 SetWidgetText(&chatOptions[CHAT_IN], newText, ChatDlg);
2273                 if(shellUp[ChatDlg]) HardSetFocus (&chatOptions[CHAT_IN], ChatDlg); //why???
2274             } else { ASSIGN(line, buf); } // box did not exist: make sure it pops up with char in it
2275             ChatPopUp();
2276         } else PopUpMoveDialog(*buf);
2277 }
2278
2279 void
2280 EchoOn ()
2281 {
2282     if(!noEcho) return;
2283     system("stty echo");
2284     WidgetEcho(&chatOptions[CHAT_IN], 1);
2285     noEcho = False;
2286 }
2287
2288 void
2289 EchoOff ()
2290 {
2291     system("stty -echo");
2292     WidgetEcho(&chatOptions[CHAT_IN], 0);
2293     noEcho = True;
2294 }
2295
2296 //--------------------------------- Game-List options dialog ------------------------------------------
2297
2298 char *strings[LPUSERGLT_SIZE];
2299 int stringPtr;
2300
2301 void
2302 GLT_ClearList ()
2303 {
2304     strings[0] = NULL;
2305     stringPtr = 0;
2306 }
2307
2308 void
2309 GLT_AddToList (char *name)
2310 {
2311     strings[stringPtr++] = name;
2312     strings[stringPtr] = NULL;
2313 }
2314
2315 Boolean
2316 GLT_GetFromList (int index, char *name)
2317 {
2318   safeStrCpy(name, strings[index], MSG_SIZ);
2319   return TRUE;
2320 }
2321
2322 void
2323 GLT_DeSelectList ()
2324 {
2325 }
2326
2327 static void GLT_Button P((int n));
2328 static int GLT_OK P((int n));
2329
2330 static Option listOptions[] = {
2331 {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
2332 { 0,    0,     0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("factory") },
2333 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("up") },
2334 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("down") },
2335 { 0, SAME_ROW, 0, NULL, (void*) &GLT_OK, "", NULL, EndMark , "" }
2336 };
2337
2338 static int
2339 GLT_OK (int n)
2340 {
2341     GLT_ParseList();
2342     appData.gameListTags = strdup(lpUserGLT);
2343     GameListUpdate();
2344     return 1;
2345 }
2346
2347 static void
2348 GLT_Button (int n)
2349 {
2350     int index = SelectedListBoxItem (&listOptions[0]);
2351     char *p;
2352     if (index < 0) {
2353         DisplayError(_("No tag selected"), 0);
2354         return;
2355     }
2356     p = strings[index];
2357     if (n == 3) {
2358         if(index >= strlen(GLT_ALL_TAGS)) return;
2359         strings[index] = strings[index+1];
2360         strings[++index] = p;
2361         LoadListBox(&listOptions[0], "?", index, index-1); // only change the two specified entries
2362     } else
2363     if (n == 2) {
2364         if(index == 0) return;
2365         strings[index] = strings[index-1];
2366         strings[--index] = p;
2367         LoadListBox(&listOptions[0], "?", index, index+1);
2368     } else
2369     if (n == 1) {
2370       safeStrCpy(lpUserGLT, GLT_DEFAULT_TAGS, LPUSERGLT_SIZE);
2371       GLT_TagsToList(lpUserGLT);
2372       index = 0;
2373       LoadListBox(&listOptions[0], "?", -1, -1);
2374     }
2375     HighlightListBoxItem(&listOptions[0], index);
2376 }
2377
2378 void
2379 GameListOptionsPopUp (DialogClass parent)
2380 {
2381     safeStrCpy(lpUserGLT, appData.gameListTags, LPUSERGLT_SIZE);
2382     GLT_TagsToList(lpUserGLT);
2383
2384     GenericPopUp(listOptions, _("Game-list options"), TransientDlg, parent, MODAL, 0);
2385 }
2386
2387 void
2388 GameListOptionsProc ()
2389 {
2390     GameListOptionsPopUp(BoardWindow);
2391 }
2392
2393 //----------------------------- Error popup in various uses -----------------------------
2394
2395 /*
2396  * [HGM] Note:
2397  * XBoard has always had some pathologic behavior with multiple simultaneous error popups,
2398  * (which can occur even for modal popups when asynchrounous events, e.g. caused by engine, request a popup),
2399  * and this new implementation reproduces that as well:
2400  * Only the shell of the last instance is remembered in shells[ErrorDlg] (which replaces errorShell),
2401  * so that PopDowns ordered from the code always refer to that instance, and once that is down,
2402  * have no clue as to how to reach the others. For the Delete Window button calling PopDown this
2403  * has now been repaired, as the action routine assigned to it gets the shell passed as argument.
2404  */
2405
2406 int errorUp = False;
2407
2408 void
2409 ErrorPopDown ()
2410 {
2411     if (!errorUp) return;
2412     dialogError = errorUp = False;
2413     PopDown(ErrorDlg); PopDown(FatalDlg); // on explicit request we pop down any error dialog
2414     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
2415 }
2416
2417 int
2418 ErrorOK (int n)
2419 {
2420     dialogError = errorUp = False;
2421     PopDown(n == 1 ? FatalDlg : ErrorDlg); // kludge: non-modal dialogs have one less (dummy) option
2422     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
2423     return FALSE; // prevent second Popdown !
2424 }
2425
2426 static Option errorOptions[] = {
2427 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // dummy option: will never be displayed
2428 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // textValue field will be set before popup
2429 { 0,NO_CANCEL,0, NULL, (void*) &ErrorOK, "", NULL, EndMark , "" }
2430 };
2431
2432 void
2433 ErrorPopUp (char *title, char *label, int modal)
2434 {
2435     errorUp = True;
2436     errorOptions[1].name = label;
2437     if(dialogError = shellUp[TransientDlg])
2438         GenericPopUp(errorOptions+1, title, FatalDlg, TransientDlg, MODAL, 0); // pop up as daughter of the transient dialog
2439     else if(dialogError = shellUp[MasterDlg])
2440         GenericPopUp(errorOptions+1, title, FatalDlg, MasterDlg, MODAL, 0); // pop up as daughter of the master dialog
2441     else
2442         GenericPopUp(errorOptions+modal, title, modal ? FatalDlg: ErrorDlg, BoardWindow, modal, 0); // kludge: option start address indicates modality
2443 }
2444
2445 void
2446 DisplayError (String message, int error)
2447 {
2448     char buf[MSG_SIZ];
2449
2450     if (error == 0) {
2451         if (appData.debugMode || appData.matchMode) {
2452             fprintf(stderr, "%s: %s\n", programName, message);
2453         }
2454     } else {
2455         if (appData.debugMode || appData.matchMode) {
2456             fprintf(stderr, "%s: %s: %s\n",
2457                     programName, message, strerror(error));
2458         }
2459         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
2460         message = buf;
2461     }
2462     ErrorPopUp(_("Error"), message, FALSE);
2463 }
2464
2465
2466 void
2467 DisplayMoveError (String message)
2468 {
2469     fromX = fromY = -1;
2470     ClearHighlights();
2471     DrawPosition(TRUE, NULL); // selective redraw would miss the from-square of the rejected move, displayed empty after drag, but not marked damaged!
2472     if (appData.debugMode || appData.matchMode) {
2473         fprintf(stderr, "%s: %s\n", programName, message);
2474     }
2475     if (appData.popupMoveErrors) {
2476         ErrorPopUp(_("Error"), message, FALSE);
2477     } else {
2478         DisplayMessage(message, "");
2479     }
2480 }
2481
2482
2483 void
2484 DisplayFatalError (String message, int error, int status)
2485 {
2486     char buf[MSG_SIZ];
2487
2488     if(status == 666) { // ignore this error when ICS Console window is up
2489         if(shellUp[ChatDlg]) return;
2490         status = 0;
2491     }
2492
2493     errorExitStatus = status;
2494     if (error == 0) {
2495         fprintf(stderr, "%s: %s\n", programName, message);
2496     } else {
2497         fprintf(stderr, "%s: %s: %s\n",
2498                 programName, message, strerror(error));
2499         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
2500         message = buf;
2501     }
2502     if(mainOptions[W_BOARD].handle) {
2503         if (appData.popupExitMessage) {
2504             if(appData.icsActive) SendToICS("logout\n"); // [HGM] make sure no new games will be started
2505             ErrorPopUp(status ? _("Fatal Error") : _("Exiting"), message, TRUE);
2506         } else {
2507             ExitEvent(status);
2508         }
2509     }
2510 }
2511
2512 void
2513 DisplayInformation (String message)
2514 {
2515     ErrorPopDown();
2516     ErrorPopUp(_("Information"), message, TRUE);
2517 }
2518
2519 void
2520 DisplayNote (String message)
2521 {
2522     ErrorPopDown();
2523     ErrorPopUp(_("Note"), message, FALSE);
2524 }
2525
2526 void
2527 DisplayTitle (char *text)
2528 {
2529     char title[MSG_SIZ];
2530     char icon[MSG_SIZ];
2531
2532     if (text == NULL) text = "";
2533
2534     if(partnerUp) { SetDialogTitle(DummyDlg, text); return; }
2535
2536     if (*text != NULLCHAR) {
2537       safeStrCpy(icon, text, sizeof(icon)/sizeof(icon[0]) );
2538       safeStrCpy(title, text, sizeof(title)/sizeof(title[0]) );
2539     } else if (appData.icsActive) {
2540         snprintf(icon, sizeof(icon), "%s", appData.icsHost);
2541         snprintf(title, sizeof(title), "%s: %s", programName, appData.icsHost);
2542     } else if (appData.cmailGameName[0] != NULLCHAR) {
2543         snprintf(icon, sizeof(icon), "%s", "CMail");
2544         snprintf(title,sizeof(title), "%s: %s", programName, "CMail");
2545 #ifdef GOTHIC
2546     // [HGM] license: This stuff should really be done in back-end, but WinBoard already had a pop-up for it
2547     } else if (gameInfo.variant == VariantGothic) {
2548       safeStrCpy(icon,  programName, sizeof(icon)/sizeof(icon[0]) );
2549       safeStrCpy(title, GOTHIC,     sizeof(title)/sizeof(title[0]) );
2550 #endif
2551 #ifdef FALCON
2552     } else if (gameInfo.variant == VariantFalcon) {
2553       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
2554       safeStrCpy(title, FALCON, sizeof(title)/sizeof(title[0]) );
2555 #endif
2556     } else if (appData.noChessProgram) {
2557       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
2558       safeStrCpy(title, programName, sizeof(title)/sizeof(title[0]) );
2559     } else {
2560       safeStrCpy(icon, first.tidy, sizeof(icon)/sizeof(icon[0]) );
2561         snprintf(title,sizeof(title), "%s: %s", programName, first.tidy);
2562     }
2563     SetWindowTitle(text, title, icon);
2564 }
2565
2566 char *textPtr;
2567 char *texEscapes[] = { "s-1", "s0", "&", "*(L", "*(R", NULL };
2568
2569 int
2570 GetNext(FILE *f)
2571 {
2572     if(textPtr) return *textPtr ? *textPtr++ : EOF;
2573     return fgetc(f);
2574 }
2575
2576 static char *
2577 ReadLine (FILE *f)
2578 {
2579     static char buf[MSG_SIZ];
2580     int i = 0, c;
2581     while((c = GetNext(f)) != '\n') { if(c == EOF) return NULL; buf[i++] = c; }
2582     buf[i] = NULLCHAR;
2583     return buf;
2584 }
2585
2586 void
2587 GetHelpText (FILE *f, char *name)
2588 {
2589     char *line, buf[MSG_SIZ], title[MSG_SIZ], text[10000], *p = text, *q = text;
2590     int len, cnt = 0;
2591     while(*name == '\n') name++;
2592     snprintf(buf, MSG_SIZ, ".B %s", name);
2593     len = strlen(buf);
2594     for(len=3; buf[len] && buf[len] != '(' && buf[len] != ':' && buf[len] != '.' && buf[len] != '?' && buf[len] != '\n'; len++);
2595     buf[len] = NULLCHAR;
2596     while(buf[--len] == ' ') buf[len] = NULLCHAR;
2597     snprintf(title, MSG_SIZ, "Help on '%s'", buf+3);
2598     while((line = ReadLine(f))) {
2599         if(!strncmp(line, buf, ++len) || !strncmp(line, ".SS ", 4) && !strncmp(line+4, buf+3, len-3)
2600                               || !strncmp(line, ".IX Item \"", 10) && !strncmp(line+10, buf+3, len-3)) {
2601             while((line = ReadLine(f)) && (cnt == 0 || strncmp(line, ".B ", 3) && strncmp(line, ".SS ", 4) && strncmp(line, ".IX ", 4))) {
2602                 if(!*line) { *p++ = '\n'; *p++ = '\n'; q = p; continue; }
2603                 if(*line == '.') continue;
2604                 *p++ = ' '; cnt++;
2605                 while(*line) {
2606                     if(*line < ' ') { line++; continue;}
2607                     if(*line == '\\') {
2608                         char **esc;
2609                         line++;
2610                         for(esc = texEscapes; *esc; esc++) {
2611                             len = strlen(*esc);
2612                             if(!strncmp(*esc, line, len)) {
2613                                 line += len;
2614                                 break;
2615                             }
2616                         }
2617                         continue;
2618                     }
2619                     if(*line == ' ' && p - q > 80) *line = '\n', q = p;
2620                     *p++ = *line++;
2621                 }
2622                 if(p - text > 9900) break;
2623             }
2624             *p = NULLCHAR;
2625             ErrorPopUp(title, text, FALSE);
2626             return;
2627         }
2628     }
2629     snprintf(text, MSG_SIZ, "No help on '%s'\n", buf+3);
2630     DisplayNote(text);
2631 }
2632
2633 void
2634 DisplayHelp (char *name)
2635 {
2636     static char *xboardMan, *manText[2], tidy[MSG_SIZ], engMan[MSG_SIZ];
2637     char buf[MSG_SIZ], adapter[MSG_SIZ], *eng;
2638     int n = 0;
2639     FILE *f;
2640     if(!xboardMan) {
2641         xboardMan = BufferCommandOutput("man -w xboard", MSG_SIZ); // obtain path to XBoard's man file
2642         if(xboardMan) xboardMan[strlen(xboardMan)-1] = NULLCHAR;   // strip off traling linefeed
2643     }
2644     if(currentCps) { // for engine options we have to look in engine manual
2645         snprintf(buf, MSG_SIZ, "man -w ");            // get (tidied) engine name in buf
2646         TidyProgramName(currentCps->program, "localhost", adapter);       // name of binary we are actually running
2647         TidyProgramName(currentCps == &first ? appData.firstChessProgram : appData.secondChessProgram, "localhost", buf+7);
2648         if(strcmp(buf+7, adapter) && StrCaseStr(name, adapter) == name) { // option starts with name of apparent proxy for engine
2649             safeStrCpy(buf+7, adapter, MSG_SIZ-7);    // use adapter manual
2650             name += strlen(adapter);                  // strip adapter name of option
2651             while(*name == ' ') name++;
2652         }
2653         if(strcmp(buf, tidy)) {                       // is different engine from last time
2654             FREE(manText[1]); manText[1] = NULL;      // so any currently held text is worthless
2655             safeStrCpy(tidy, buf, MSG_SIZ);           // remember current engine
2656             eng = BufferCommandOutput(tidy, MSG_SIZ); // obtain path to  its man file
2657             safeStrCpy(engMan, eng, strlen(eng));     // and remember that too
2658             FREE(eng);
2659         }
2660         safeStrCpy(buf, engMan, MSG_SIZ); n = 1;      // use engine man
2661     } else snprintf(buf, MSG_SIZ, "%s", xboardMan);   // use xboard man
2662     f = fopen(buf, "r");
2663     if(f) {
2664         char *msg = "Right-clicking menu item or dialog text pops up help on it";
2665         ASSIGN(appData.suppress, msg);
2666         if(strstr(buf, ".gz")) { // man file is gzipped
2667             if(!manText[n]) {    // unzipped text not buffered yet
2668                 snprintf(tidy, MSG_SIZ, "gunzip -c %s", buf);
2669                 manText[n] = BufferCommandOutput(tidy, 250000); // store unzipped in buffer
2670             }
2671             textPtr = manText[n];// use buffered unzipped text
2672         } else textPtr = NULL;   // use plaintext man file directly
2673         GetHelpText(f, name);
2674         fclose(f);
2675     } else if(currentCps) DisplayNote("No manual is installed for this engine");
2676 }
2677
2678 #define PAUSE_BUTTON "P"
2679 #define PIECE_MENU_SIZE 18
2680 static String pieceMenuStrings[2][PIECE_MENU_SIZE+1] = {
2681     { N_("White"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
2682       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
2683       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
2684       N_("Empty square"), N_("Clear board"), NULL },
2685     { N_("Black"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
2686       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
2687       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
2688       N_("Empty square"), N_("Clear board"), NULL }
2689 };
2690 /* must be in same order as pieceMenuStrings! */
2691 static ChessSquare pieceMenuTranslation[2][PIECE_MENU_SIZE] = {
2692     { WhitePlay, (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2693         WhiteRook, WhiteQueen, WhiteKing, (ChessSquare) 0, WhiteAlfil,
2694         WhiteCannon, WhiteAngel, WhiteMarshall, (ChessSquare) 0,
2695         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2696     { BlackPlay, (ChessSquare) 0, BlackPawn, BlackKnight, BlackBishop,
2697         BlackRook, BlackQueen, BlackKing, (ChessSquare) 0, BlackAlfil,
2698         BlackCannon, BlackAngel, BlackMarshall, (ChessSquare) 0,
2699         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2700 };
2701
2702 #define DROP_MENU_SIZE 6
2703 static String dropMenuStrings[DROP_MENU_SIZE+1] = {
2704     "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"), N_("Queen"), NULL
2705   };
2706 /* must be in same order as dropMenuStrings! */
2707 static ChessSquare dropMenuTranslation[DROP_MENU_SIZE] = {
2708     (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2709     WhiteRook, WhiteQueen
2710 };
2711
2712 // [HGM] experimental code to pop up window just like the main window, using GenercicPopUp
2713
2714 static Option *Exp P((int n, int x, int y));
2715 void MenuCallback P((int n));
2716 void SizeKludge P((int n));
2717 static Option *LogoW P((int n, int x, int y));
2718 static Option *LogoB P((int n, int x, int y));
2719
2720 static int pmFromX = -1, pmFromY = -1;
2721 void *userLogo;
2722
2723 void
2724 DisplayLogos (Option *w1, Option *w2)
2725 {
2726         void *whiteLogo = first.programLogo, *blackLogo = second.programLogo;
2727         if(appData.autoLogo) {
2728           if(appData.noChessProgram) whiteLogo = blackLogo = NULL;
2729           if(appData.icsActive) whiteLogo = blackLogo = second.programLogo;
2730           switch(gameMode) { // pick logos based on game mode
2731             case IcsObserving:
2732                 whiteLogo = second.programLogo; // ICS logo
2733                 blackLogo = second.programLogo;
2734             default:
2735                 break;
2736             case IcsPlayingWhite:
2737                 if(!appData.zippyPlay) whiteLogo = userLogo;
2738                 blackLogo = second.programLogo; // ICS logo
2739                 break;
2740             case IcsPlayingBlack:
2741                 whiteLogo = second.programLogo; // ICS logo
2742                 blackLogo = appData.zippyPlay ? first.programLogo : userLogo;
2743                 break;
2744             case TwoMachinesPlay:
2745                 if(first.twoMachinesColor[0] == 'b') {
2746                     whiteLogo = second.programLogo;
2747                     blackLogo = first.programLogo;
2748                 }
2749                 break;
2750             case MachinePlaysWhite:
2751                 blackLogo = userLogo;
2752                 break;
2753             case MachinePlaysBlack:
2754                 whiteLogo = userLogo;
2755                 blackLogo = first.programLogo;
2756           }
2757         }
2758         DrawLogo(w1, whiteLogo);
2759         DrawLogo(w2, blackLogo);
2760 }
2761
2762 static void
2763 PMSelect (int n)
2764 {   // user callback for board context menus
2765     if (pmFromX < 0 || pmFromY < 0) return;
2766     if(n == W_DROP) DropMenuEvent(dropMenuTranslation[values[n]], pmFromX, pmFromY);
2767     else EditPositionMenuEvent(pieceMenuTranslation[n - W_MENUW][values[n]], pmFromX, pmFromY);
2768 }
2769
2770 static void
2771 CCB (int n)
2772 {
2773     shiftKey = (ShiftKeys() & 3) != 0;
2774     if(n < 0) { // button != 1
2775         n = -n;
2776         if(shiftKey && (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack)) {
2777             AdjustClock(n == W_BLACK, 1);
2778         }
2779     } else
2780     ClockClick(n == W_BLACK);
2781 }
2782
2783 Option mainOptions[] = { // description of main window in terms of generic dialog creator
2784 { 0, 0xCA, 0, NULL, NULL, "", NULL, BarBegin, "" }, // menu bar
2785   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_File") },
2786   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Edit") },
2787   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_View") },
2788   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Mode") },
2789   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Action") },
2790   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("E_ngine") },
2791   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Options") },
2792   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Help") },
2793 { 0, 0, 0, NULL, (void*)&SizeKludge, "", NULL, BarEnd, "" },
2794 { 0, LR|T2T|BORDER|SAME_ROW, 0, NULL, NULL, NULL, NULL, Label, "1" }, // optional title in window
2795 { 50,    LL|TT,            100, NULL, (void*) &LogoW, NULL, NULL, Skip, "" }, // white logo
2796 { 12,   L2L|T2T,           200, NULL, (void*) &CCB, NULL, NULL, Label, "White" }, // white clock
2797 { 13,   R2R|T2T|SAME_ROW,  200, NULL, (void*) &CCB, NULL, NULL, Label, "Black" }, // black clock
2798 { 50,    RR|TT|SAME_ROW,   100, NULL, (void*) &LogoB, NULL, NULL, Skip, "" }, // black logo
2799 { 0, LR|T2T|BORDER,        401, NULL, NULL, "", NULL, Skip, "2" }, // backup for title in window (if no room for other)
2800 { 0, LR|T2T|BORDER,        270, NULL, NULL, NULL, NULL, Label, "message", &appData.font }, // message field
2801 { 0, RR|TT|SAME_ROW,       125, NULL, NULL, "", NULL, BoxBegin, "" }, // (optional) button bar
2802   { 0,    0,     0, NULL, (void*) &ToStartEvent,  NULL, NULL, Button, N_("<<"), &appData.font },
2803   { 0, SAME_ROW, 0, NULL, (void*) &BackwardEvent, NULL, NULL, Button, N_("<"),  &appData.font },
2804   { 0, SAME_ROW, 0, NULL, (void*) &PauseEvent,    NULL, NULL, Button, N_(PAUSE_BUTTON), &appData.font },
2805   { 0, SAME_ROW, 0, NULL, (void*) &ForwardEvent,  NULL, NULL, Button, N_(">"),  &appData.font },
2806   { 0, SAME_ROW, 0, NULL, (void*) &ToEndEvent,    NULL, NULL, Button, N_(">>"), &appData.font },
2807 { 0, 0, 0, NULL, NULL, "", NULL, BoxEnd, "" },
2808 { 401, LR|TB, 401, NULL, (char*) &Exp, NULL, NULL, Graph, "shadow board" }, // board
2809   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[0], PopUp, "menuW" },
2810   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[1], PopUp, "menuB" },
2811   { -1, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, dropMenuStrings, PopUp, "menuD" },
2812 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2813 };
2814
2815 Option *
2816 LogoW (int n, int x, int y)
2817 {
2818     if(n == 10) DisplayLogos(&mainOptions[W_WHITE-1], NULL);
2819     return NULL;
2820 }
2821
2822 Option *
2823 LogoB (int n, int x, int y)
2824 {
2825     if(n == 10) DisplayLogos(NULL, &mainOptions[W_BLACK+1]);
2826     return NULL;
2827 }
2828
2829 void
2830 SizeKludge (int n)
2831 {   // callback called by GenericPopUp immediately after sizing the menu bar
2832     int width = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2833     int w = width - 44 - mainOptions[n].min;
2834     mainOptions[W_TITLE].max = w; // width left behind menu bar
2835     if(w < 0.4*width) // if no reasonable amount of space for title, force small layout
2836         mainOptions[W_SMALL].type = mainOptions[W_TITLE].type, mainOptions[W_TITLE].type = Skip;
2837 }
2838
2839 void
2840 MenuCallback (int n)
2841 {
2842     MenuProc *proc = (MenuProc *) (((MenuItem*)(mainOptions[n].choice))[values[n]].proc);
2843
2844     if(!proc) RecentEngineEvent(values[n] - firstEngineItem); else (proc)();
2845 }
2846
2847 static Option *
2848 Exp (int n, int x, int y)
2849 {
2850     static int but1, but3, oldW, oldH, oldX, oldY;
2851     int menuNr = -3, sizing, f, r;
2852     TimeMark now;
2853     extern Boolean right;
2854
2855     if(right) {  // kludgy way to let button 1 double as button 3 when back-end requests this
2856         if(but1 && n == 0) but1 = 0, but3 = 1;
2857         else if(n == -1) n = -3, right = FALSE;
2858     }
2859
2860     if(n == 0) { // motion
2861         oldX = x; oldY = y;
2862         if(SeekGraphClick(Press, x, y, 1)) return NULL;
2863         if((but1 || dragging == 2) && !PromoScroll(x, y)) DragPieceMove(x, y);
2864         if(but3) MovePV(x, y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
2865         if(appData.highlightDragging) {
2866             f = EventToSquare(x, BOARD_WIDTH);  if ( flipView && f >= 0) f = BOARD_WIDTH - 1 - f;
2867             r = EventToSquare(y, BOARD_HEIGHT); if (!flipView && r >= 0) r = BOARD_HEIGHT - 1 - r;
2868             HoverEvent(x, y, f, r);
2869         }
2870         return NULL;
2871     }
2872     if(n != 10 && PopDown(PromoDlg)) fromX = fromY = -1; // user starts fiddling with board when promotion dialog is up
2873     else GetTimeMark(&now);
2874     shiftKey = ShiftKeys();
2875     controlKey = (shiftKey & 0xC) != 0;
2876     shiftKey = (shiftKey & 3) != 0;
2877     switch(n) {
2878         case  1: LeftClick(Press,   x, y), but1 = 1; break;
2879         case -1: LeftClick(Release, x, y), but1 = 0; break;
2880         case  2: shiftKey = !shiftKey;
2881         case  3: menuNr = RightClick(Press,   x, y, &pmFromX, &pmFromY), but3 = 1; break;
2882         case -2: shiftKey = !shiftKey;
2883         case -3: menuNr = RightClick(Release, x, y, &pmFromX, &pmFromY), but3 = 0; break;
2884         case  4: Wheel(-1, oldX, oldY); break;
2885         case  5: Wheel(1, oldX, oldY); break;
2886         case 10:
2887             sizing = (oldW != x || oldH != y);
2888             oldW = x; oldH = y;
2889             InitDrawingHandle(mainOptions + W_BOARD);
2890             if(sizing && SubtractTimeMarks(&now, &programStartTime) > 10000) return NULL; // don't redraw while sizing (except at startup)
2891             DrawPosition(True, NULL);
2892         default:
2893             return NULL;
2894     }
2895
2896     switch(menuNr) {
2897       case 0: return &mainOptions[shiftKey ? W_MENUW: W_MENUB];
2898       case 1: SetupDropMenu(); return &mainOptions[W_DROP];
2899       case 2:
2900       case -1: ErrorPopDown();
2901       case -2:
2902       default: break; // -3, so no clicks caught
2903     }
2904     return NULL;
2905 }
2906
2907 Option *
2908 BoardPopUp (int squareSize, int lineGap, void *clockFontThingy)
2909 {
2910     int i, size = BOARD_WIDTH*(squareSize + lineGap) + lineGap, logo = appData.logoSize;
2911     int f = 2*appData.fixedSize; // width fudge, needed for unknown reasons to not clip board
2912     mainOptions[W_WHITE].choice = (char**) clockFontThingy;
2913     mainOptions[W_BLACK].choice = (char**) clockFontThingy;
2914     mainOptions[W_BOARD].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2915     mainOptions[W_BOARD].max = mainOptions[W_SMALL].max = size; // board size
2916     mainOptions[W_SMALL].max = size - 2; // board title (subtract border!)
2917     mainOptions[W_BLACK].max = mainOptions[W_WHITE].max = size/2-3; // clock width
2918     mainOptions[W_MESSG].max = appData.showButtonBar ? size-135+f : size-2+f; // message
2919     mainOptions[W_MENU].max = size-40; // menu bar
2920     mainOptions[W_TITLE].type = appData.titleInWindow ? Label : Skip ;
2921     if(logo && logo <= size/4) { // Activate logos
2922         mainOptions[W_WHITE-1].type = mainOptions[W_BLACK+1].type = Graph;
2923         mainOptions[W_WHITE-1].max  = mainOptions[W_BLACK+1].max  = logo;
2924         mainOptions[W_WHITE-1].value= mainOptions[W_BLACK+1].value= logo/2;
2925         mainOptions[W_WHITE].min  |= SAME_ROW;
2926         mainOptions[W_WHITE].max  = mainOptions[W_BLACK].max  -= logo + 4;
2927         mainOptions[W_WHITE].name = mainOptions[W_BLACK].name = "Double\nHeight";
2928     }
2929     if(!appData.showButtonBar) for(i=W_BUTTON; i<W_BOARD; i++) mainOptions[i].type = Skip;
2930     for(i=0; i<8; i++) mainOptions[i+1].choice = (char**) menuBar[i].mi;
2931     AppendEnginesToMenu(appData.recentEngineList);
2932     GenericPopUp(mainOptions, "XBoard", BoardWindow, BoardWindow, NONMODAL, 1); // allways top-level
2933     return mainOptions;
2934 }
2935
2936 static Option *
2937 SlaveExp (int n, int x, int y)
2938 {
2939     if(n == 10) { // expose event
2940         flipView = !flipView; partnerUp = !partnerUp;
2941         DrawPosition(True, NULL); // [HGM] dual: draw other board in other orientation
2942         flipView = !flipView; partnerUp = !partnerUp;
2943     }
2944     return NULL;
2945 }
2946
2947 Option dualOptions[] = { // auxiliary board window
2948 { 0, L2L|T2T,              198, NULL, NULL, NULL, NULL, Label, "White" }, // white clock
2949 { 0, R2R|T2T|SAME_ROW,     198, NULL, NULL, NULL, NULL, Label, "Black" }, // black clock
2950 { 0, LR|T2T|BORDER,        401, NULL, NULL, NULL, NULL, Label, "This feature is experimental" }, // message field
2951 { 401, LR|TT, 401, NULL, (char*) &SlaveExp, NULL, NULL, Graph, "shadow board" }, // board
2952 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2953 };
2954
2955 void
2956 SlavePopUp ()
2957 {
2958     int size = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2959     // copy params from main board
2960     dualOptions[0].choice = mainOptions[W_WHITE].choice;
2961     dualOptions[1].choice = mainOptions[W_BLACK].choice;
2962     dualOptions[3].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2963     dualOptions[3].max = dualOptions[2].max = size; // board width
2964     dualOptions[0].max = dualOptions[1].max = size/2 - 3; // clock width
2965     GenericPopUp(dualOptions, "XBoard", DummyDlg, BoardWindow, NONMODAL, appData.topLevel);
2966     SlaveResize(dualOptions+3);
2967 }
2968
2969 void
2970 DisplayWhiteClock (long timeRemaining, int highlight)
2971 {
2972     if(appData.noGUI) return;
2973     if(twoBoards && partnerUp) {
2974         DisplayTimerLabel(&dualOptions[0], _("White"), timeRemaining, highlight);
2975         return;
2976     }
2977     DisplayTimerLabel(&mainOptions[W_WHITE], _("White"), timeRemaining, highlight);
2978     if(highlight) SetClockIcon(0);
2979 }
2980
2981 void
2982 DisplayBlackClock (long timeRemaining, int highlight)
2983 {
2984     if(appData.noGUI) return;
2985     if(twoBoards && partnerUp) {
2986         DisplayTimerLabel(&dualOptions[1], _("Black"), timeRemaining, highlight);
2987         return;
2988     }
2989     DisplayTimerLabel(&mainOptions[W_BLACK], _("Black"), timeRemaining, highlight);
2990     if(highlight) SetClockIcon(1);
2991 }
2992
2993
2994 //---------------------------------------------
2995
2996 void
2997 DisplayMessage (char *message, char *extMessage)
2998 {
2999   /* display a message in the message widget */
3000
3001   char buf[MSG_SIZ];
3002
3003   if (extMessage)
3004     {
3005       if (*message)
3006         {
3007           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
3008           message = buf;
3009         }
3010       else
3011         {
3012           message = extMessage;
3013         };
3014     };
3015
3016     safeStrCpy(lastMsg, message, MSG_SIZ); // [HGM] make available
3017
3018   /* need to test if messageWidget already exists, since this function
3019      can also be called during the startup, if for example a Xresource
3020      is not set up correctly */
3021   if(mainOptions[W_MESSG].handle)
3022     SetWidgetLabel(&mainOptions[W_MESSG], message);
3023
3024   return;
3025 }
3026
3027 //----------------------------------- File Browser -------------------------------
3028
3029 #ifdef HAVE_DIRENT_H
3030 #include <dirent.h>
3031 #else
3032 #include <sys/dir.h>
3033 #define dirent direct
3034 #endif
3035
3036 #include <sys/stat.h>
3037
3038 #define MAXFILES 1000
3039
3040 static DialogClass savDlg;
3041 static ChessProgramState *savCps;
3042 static FILE **savFP;
3043 static char *fileName, *extFilter, *savMode, **namePtr;
3044 static int folderPtr, filePtr, oldVal, byExtension, extFlag, pageStart, cnt;
3045 static char curDir[MSG_SIZ], title[MSG_SIZ], *folderList[MAXFILES], *fileList[MAXFILES];
3046
3047 static char *FileTypes[] = {
3048 "Chess Games",
3049 "Chess Positions",
3050 "Tournaments",
3051 "Opening Books",
3052 "Sound files",
3053 "Images",
3054 "Settings (*.ini)",
3055 "Log files",
3056 "All files",
3057 NULL,
3058 "PGN",
3059 "Old-Style Games",
3060 "FEN",
3061 "Old-Style Positions",
3062 NULL,
3063 NULL
3064 };
3065
3066 static char *Extensions[] = {
3067 ".pgn .game",
3068 ".fen .epd .pos",
3069 ".trn",
3070 ".bin",
3071 ".wav",
3072 ".png",
3073 ".ini",
3074 ".log",
3075 "",
3076 "INVALID",
3077 ".pgn",
3078 ".game",
3079 ".fen",
3080 ".pos",
3081 NULL,
3082 ""
3083 };
3084
3085 void DirSelProc P((int n, int sel));
3086 void FileSelProc P((int n, int sel));
3087 void SetTypeFilter P((int n));
3088 int BrowseOK P((int n));
3089 void Switch P((int n));
3090 void CreateDir P((int n));
3091
3092 Option browseOptions[] = {
3093 {   0,    LR|T2T,      500, NULL, NULL, NULL, NULL, Label, title },
3094 {   0,    L2L|T2T,     250, NULL, NULL, NULL, NULL, Label, N_("Directories:") },
3095 {   0,R2R|T2T|SAME_ROW,100, NULL, NULL, NULL, NULL, Label, N_("Files:") },
3096 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by name") },
3097 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by type") },
3098 { 300,    L2L|TB,      250, NULL, (void*) folderList, (char*) &DirSelProc, NULL, ListBox, "" },
3099 { 300, R2R|TB|SAME_ROW,250, NULL, (void*) fileList, (char*) &FileSelProc, NULL, ListBox, "" },
3100 {   0,       0,        300, NULL, (void*) &fileName, NULL, NULL, TextBox, N_("Filename:") },
3101 {   0,    SAME_ROW,    120, NULL, (void*) &CreateDir, NULL, NULL, Button, N_("New directory") },
3102 {   0, COMBO_CALLBACK, 150, NULL, (void*) &SetTypeFilter, NULL, FileTypes, ComboBox, N_("File type:") },
3103 {   0,    SAME_ROW,      0, NULL, (void*) &BrowseOK, "", NULL, EndMark , "" }
3104 };
3105
3106 int
3107 BrowseOK (int n)
3108 {
3109         if(!fileName[0]) { // it is enough to have a file selected
3110             if(browseOptions[6].textValue) { // kludge: if callback specified we browse for file
3111                 int sel = SelectedListBoxItem(&browseOptions[6]);
3112                 if(sel < 0 || sel >= filePtr) return FALSE;
3113                 ASSIGN(fileName, fileList[sel]);
3114             } else { // we browse for path
3115                 ASSIGN(fileName, curDir); // kludge: without callback we browse for path
3116             }
3117         }
3118         if(!fileName[0]) return FALSE; // refuse OK when no file
3119         if(!savMode[0]) { // browsing for name only (dialog Browse button)
3120                 if(fileName[0] == '/') // We already had a path name
3121                     snprintf(title, MSG_SIZ, "%s", fileName);
3122                 else
3123                     snprintf(title, MSG_SIZ, "%s/%s", curDir, fileName);
3124                 SetWidgetText((Option*) savFP, title, savDlg);
3125                 currentCps = savCps; // could return to Engine Settings dialog!
3126                 return TRUE;
3127         }
3128         *savFP = fopen(fileName, savMode);
3129         if(*savFP == NULL) return FALSE; // refuse OK if file not openable
3130         ASSIGN(*namePtr, fileName);
3131         ScheduleDelayedEvent(DelayedLoad, 50);
3132         currentCps = savCps; // not sure this is ever non-null
3133         return TRUE;
3134 }
3135
3136 int
3137 AlphaNumCompare (char *p, char *q)
3138 {
3139     while(*p) {
3140         if(isdigit(*p) && isdigit(*q) && atoi(p) != atoi(q))
3141              return (atoi(p) > atoi(q) ? 1 : -1);
3142         if(*p != *q) break;
3143         p++, q++;
3144     }
3145     if(*p == *q) return 0;
3146     return (*p > *q ? 1 : -1);
3147 }
3148
3149 int
3150 Comp (const void *s, const void *t)
3151 {
3152     char *p = *(char**) s, *q = *(char**) t;
3153     if(extFlag) {
3154         char *h; int r;
3155         while(h = strchr(p, '.')) p = h+1;
3156         if(p == *(char**) s) p = "";
3157         while(h = strchr(q, '.')) q = h+1;
3158         if(q == *(char**) t) q = "";
3159         r = AlphaNumCompare(p, q);
3160         if(r) return r;
3161     }
3162     return AlphaNumCompare( *(char**) s, *(char**) t );
3163 }
3164
3165 void
3166 ListDir (int pathFlag)
3167 {
3168         DIR *dir;
3169         struct dirent *dp;
3170         struct stat statBuf;
3171         static int lastFlag;
3172
3173         if(pathFlag < 0) pathFlag = lastFlag;
3174         lastFlag = pathFlag;
3175         dir = opendir(".");
3176         getcwd(curDir, MSG_SIZ);
3177         snprintf(title, MSG_SIZ, "%s   %s", _("Contents of"), curDir);
3178         folderPtr = filePtr = cnt = 0; // clear listing
3179
3180         while (dp = readdir(dir)) { // pass 1: list foders
3181             char *s = dp->d_name;
3182             if(!stat(s, &statBuf) && S_ISDIR(statBuf.st_mode)) { // stat succeeds and tells us it is directory
3183                 if(s[0] == '.' && strcmp(s, "..")) continue; // suppress hidden, except ".."
3184                 ASSIGN(folderList[folderPtr], s); if(folderPtr < MAXFILES-2) folderPtr++;
3185             } else if(!pathFlag) {
3186                 char *s = dp->d_name, match=0;
3187 //              if(cnt == pageStart) { ASSIGN }
3188                 if(s[0] == '.') continue; // suppress hidden files
3189                 if(extFilter[0]) { // [HGM] filter on extension
3190                     char *p = extFilter, *q;
3191                     do {
3192                         if(q = strchr(p, ' ')) *q = 0;
3193                         if(strstr(s, p)) match++;
3194                         if(q) *q = ' ';
3195                     } while(q && (p = q+1));
3196                     if(!match) continue;
3197                 }
3198                 if(filePtr == MAXFILES-2) continue;
3199                 if(cnt++ < pageStart) continue;
3200                 ASSIGN(fileList[filePtr], s); filePtr++;
3201             }
3202         }
3203         if(filePtr == MAXFILES-2) { ASSIGN(fileList[filePtr], _("  next page")); filePtr++; }
3204         FREE(folderList[folderPtr]); folderList[folderPtr] = NULL;
3205         FREE(fileList[filePtr]); fileList[filePtr] = NULL;
3206         closedir(dir);
3207         extFlag = 0;         qsort((void*)folderList, folderPtr, sizeof(char*), &Comp);
3208         extFlag = byExtension; qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
3209 }
3210
3211 void
3212 Refresh (int pathFlag)
3213 {
3214     ListDir(pathFlag); // and make new one
3215     LoadListBox(&browseOptions[5], "", -1, -1);
3216     LoadListBox(&browseOptions[6], "", -1, -1);
3217     SetWidgetLabel(&browseOptions[0], title);
3218 }
3219
3220 static char msg1[] = N_("FIRST TYPE DIRECTORY NAME HERE");
3221 static char msg2[] = N_("TRY ANOTHER NAME");
3222
3223 void
3224 CreateDir (int n)
3225 {
3226     char *name, *errmsg = "";
3227     GetWidgetText(&browseOptions[n-1], &name);
3228     if(!strcmp(name, msg1) || !strcmp(name, msg2)) return;
3229     if(!name[0]) errmsg = _(msg1); else
3230     if(mkdir(name, 0755)) errmsg = _(msg2);
3231     else {
3232         chdir(name);
3233         Refresh(-1);
3234     }
3235     SetWidgetText(&browseOptions[n-1], errmsg, BrowserDlg);
3236 }
3237
3238 void
3239 Switch (int n)
3240 {
3241     if(byExtension == (n == 4)) return;
3242     extFlag = byExtension = (n == 4);
3243     qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
3244     LoadListBox(&browseOptions[6], "", -1, -1);
3245 }
3246
3247 void
3248 SetTypeFilter (int n)
3249 {
3250     int j = values[n];
3251     if(j == browseOptions[n].value) return; // no change
3252     browseOptions[n].value = j;
3253     SetWidgetLabel(&browseOptions[n], FileTypes[j]);
3254     ASSIGN(extFilter, Extensions[j]);
3255     pageStart = 0;
3256     Refresh(-1); // uses pathflag remembered by ListDir
3257     values[n] = oldVal; // do not disturb combo settings of underlying dialog
3258 }
3259
3260 void
3261 FileSelProc (int n, int sel)
3262 {
3263     if(sel < 0 || fileList[sel] == NULL) return;
3264     if(sel == MAXFILES-2) { pageStart = cnt; Refresh(-1); return; }
3265     ASSIGN(fileName, fileList[sel]);
3266     if(BrowseOK(0)) PopDown(BrowserDlg);
3267 }
3268
3269 void
3270 DirSelProc (int n, int sel)
3271 {
3272     if(!chdir(folderList[sel])) { // cd succeeded, so we are in new directory now
3273         Refresh(-1);
3274     }
3275 }
3276
3277 void
3278 StartDir (char *filter, char *newName)
3279 {
3280     static char *gamesDir, *trnDir, *imgDir, *bookDir;
3281     static char curDir[MSG_SIZ];
3282     char **res = NULL;
3283     if(!filter || !*filter) return;
3284     if(strstr(filter, "pgn")) res = &gamesDir; else
3285     if(strstr(filter, "bin")) res = &bookDir; else
3286     if(strstr(filter, "png")) res = &imgDir; else
3287     if(strstr(filter, "trn")) res = &trnDir; else
3288     if(strstr(filter, "fen")) res = &appData.positionDir;
3289     if(res) {
3290         if(newName) {
3291             char *p, *q;
3292             if(*newName) {
3293                 ASSIGN(*res, newName);
3294                 for(p=*res; q=strchr(p, '/');) p = q + 1; *p = NULLCHAR;
3295             }
3296             if(*curDir) chdir(curDir);
3297             *curDir = NULLCHAR;
3298         } else {
3299             getcwd(curDir, MSG_SIZ);
3300             if(*res && **res) chdir(*res);
3301         }
3302     }
3303 }
3304
3305 void
3306 Browse (DialogClass dlg, char *label, char *proposed, char *ext, Boolean pathFlag, char *mode, char **name, FILE **fp)
3307 {
3308     int j=0;
3309     savFP = fp; savMode = mode, namePtr = name, savCps = currentCps, oldVal = values[9], savDlg = dlg; // save params, for use in callback
3310     ASSIGN(extFilter, ext);
3311     ASSIGN(fileName, proposed ? proposed : "");
3312     for(j=0; Extensions[j]; j++) // look up actual value in list of possible values, to get selection nr
3313         if(extFilter && !strcmp(extFilter, Extensions[j])) break;
3314     if(Extensions[j] == NULL) { j++; ASSIGN(FileTypes[j], extFilter); }
3315     browseOptions[9].value = j;
3316     browseOptions[6].textValue = (char*) (pathFlag ? NULL : &FileSelProc); // disable file listbox during path browsing
3317     pageStart = 0; ListDir(pathFlag);
3318     currentCps = NULL;
3319     GenericPopUp(browseOptions, label, BrowserDlg, dlg, MODAL, 0);
3320     SetWidgetLabel(&browseOptions[9], FileTypes[j]);
3321 }
3322
3323 static char *openName;
3324 FileProc fileProc;
3325 char *fileOpenMode;
3326 FILE *openFP;
3327
3328 void
3329 DelayedLoad ()
3330 {
3331   (void) (*fileProc)(openFP, 0, openName);
3332 }
3333
3334 void
3335 FileNamePopUp (char *label, char *def, char *filter, FileProc proc, char *openMode)
3336 {
3337     fileProc = proc;            /* I can't see a way not */
3338     fileOpenMode = openMode;    /*   to use globals here */
3339     FileNamePopUpWrapper(label, def, filter, proc, False, openMode, &openName, &openFP);
3340 }
3341
3342 void
3343 ActivateTheme (int col)
3344 {
3345     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap; else lineGap = defaultLineGap;
3346     InitDrawingParams(strcmp(oldPieceDir, appData.pieceDirectory));
3347     InitDrawingSizes(-1, 0);
3348     DrawPosition(True, NULL);
3349 }
3350