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