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