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