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