Add 'divide by 60' checkbox in Time Control dialog 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, 2016 Free Software Foundation, Inc.
5  * ------------------------------------------------------------------------
6  *
7  * GNU XBoard is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or (at
10  * your option) any later version.
11  *
12  * GNU XBoard is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program. If not, see http://www.gnu.org/licenses/.  *
19  *
20  *------------------------------------------------------------------------
21  ** See the file ChangeLog for a revision history.  */
22
23 // [HGM] this file is the counterpart of woptions.c, containing xboard popup menus
24 // similar to those of WinBoard, to set the most common options interactively.
25
26 #include "config.h"
27
28 #include <stdio.h>
29 #include <ctype.h>
30 #include <errno.h>
31 #include <sys/types.h>
32
33 #if STDC_HEADERS
34 # include <stdlib.h>
35 # include <string.h>
36 #else /* not STDC_HEADERS */
37 extern char *getenv();
38 # if HAVE_STRING_H
39 #  include <string.h>
40 # else /* not HAVE_STRING_H */
41 #  include <strings.h>
42 # endif /* not HAVE_STRING_H */
43 #endif /* not STDC_HEADERS */
44
45 #if HAVE_UNISTD_H
46 # include <unistd.h>
47 #endif
48 #include <stdint.h>
49
50 #include "common.h"
51 #include "frontend.h"
52 #include "backend.h"
53 #include "xboard2.h"
54 #include "menus.h"
55 #include "dialogs.h"
56 #include "gettext.h"
57
58 #ifdef ENABLE_NLS
59 # define  _(s) gettext (s)
60 # define N_(s) gettext_noop (s)
61 #else
62 # define  _(s) (s)
63 # define N_(s)  s
64 #endif
65
66
67 int values[MAX_OPTIONS];
68 ChessProgramState *currentCps;
69
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, by60;
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,    0, NULL, (void*) &by60,     "",  NULL, CheckBox, N_("Divide entered times by 60") },
1631 {   0,  0,  200, NULL, (void*) &tmpMoves, NULL, NULL, Spin, N_("Moves per session:") },
1632 {   0,  0,10000, NULL, (void*) &tmpTc,    NULL, NULL, Spin, N_("Initial time (min):") },
1633 {   0, 0, 10000, NULL, (void*) &tmpInc,   NULL, NULL, Spin, N_("Increment or max (sec/move):") },
1634 {   0,  0,    0, NULL, NULL, NULL, NULL, Label, N_("Time-Odds factors:") },
1635 {   0,  1, 1000, NULL, (void*) &tmpOdds1, NULL, NULL, Spin, N_("Engine #1") },
1636 {   0,  1, 1000, NULL, (void*) &tmpOdds2, NULL, NULL, Spin, N_("Engine #2 / Human") },
1637 {   0,  0,    0, NULL, (void*) &TcOK, "", NULL, EndMark , "" }
1638 };
1639
1640 static int
1641 TcOK (int n)
1642 {
1643     char *tc, buf[MSG_SIZ];
1644     if(tcType == 0 && tmpMoves <= 0) return 0;
1645     if(tcType == 2 && tmpInc <= 0) return 0;
1646     GetWidgetText(&tcOptions[5], &tc); // get original text, in case it is min:sec
1647     if(by60) snprintf(buf, MSG_SIZ, "%d:%02d", tmpTc/60, tmpTc%60), tc=buf;
1648     searchTime = 0;
1649     switch(tcType) {
1650       case 0:
1651         if(!ParseTimeControl(tc, -1, tmpMoves)) return 0;
1652         appData.movesPerSession = tmpMoves;
1653         ASSIGN(appData.timeControl, tc);
1654         appData.timeIncrement = -1;
1655         break;
1656       case 1:
1657         if(!ParseTimeControl(tc, tmpInc, 0)) return 0;
1658         ASSIGN(appData.timeControl, tc);
1659         appData.timeIncrement = (by60 ? tmpInc/60. : tmpInc);
1660         break;
1661       case 2:
1662         searchTime = (by60 ? tmpInc/60 : tmpInc);
1663     }
1664     appData.firstTimeOdds = first.timeOdds = tmpOdds1;
1665     appData.secondTimeOdds = second.timeOdds = tmpOdds2;
1666     Reset(True, True);
1667     return 1;
1668 }
1669
1670 static void
1671 SetTcType (int n)
1672 {
1673     switch(tcType = n) {
1674       case 0:
1675         SetWidgetText(&tcOptions[4], Value(tmpMoves), TransientDlg);
1676         SetWidgetText(&tcOptions[5], Value(tmpTc), TransientDlg);
1677         SetWidgetText(&tcOptions[6], _("Unused"), TransientDlg);
1678         break;
1679       case 1:
1680         SetWidgetText(&tcOptions[4], _("Unused"), TransientDlg);
1681         SetWidgetText(&tcOptions[5], Value(tmpTc), TransientDlg);
1682         SetWidgetText(&tcOptions[6], Value(tmpInc), TransientDlg);
1683         break;
1684       case 2:
1685         SetWidgetText(&tcOptions[4], _("Unused"), TransientDlg);
1686         SetWidgetText(&tcOptions[5], _("Unused"), TransientDlg);
1687         SetWidgetText(&tcOptions[6], Value(tmpInc), TransientDlg);
1688     }
1689 }
1690
1691 void
1692 TimeControlProc ()
1693 {
1694    if(gameMode != BeginningOfGame) {
1695         DisplayError(_("Changing time control during a game is not implemented"), 0);
1696         return;
1697    }
1698    tmpMoves = appData.movesPerSession;
1699    tmpInc = appData.timeIncrement; if(tmpInc < 0) tmpInc = 0;
1700    tmpOdds1 = tmpOdds2 = 1; tcType = 0;
1701    tmpTc = atoi(appData.timeControl);
1702    by60 = 0;
1703    GenericPopUp(tcOptions, _("Time Control"), TransientDlg, BoardWindow, MODAL, 0);
1704    SetTcType(searchTime ? 2 : appData.timeIncrement < 0 ? 0 : 1);
1705 }
1706
1707 //------------------------------- Ask Question -----------------------------------------
1708
1709 int SendReply P((int n));
1710 char pendingReplyPrefix[MSG_SIZ];
1711 ProcRef pendingReplyPR;
1712 char *answer;
1713
1714 Option askOptions[] = {
1715 { 0, 0, 0, NULL, NULL, NULL, NULL, Label,  NULL },
1716 { 0, 0, 0, NULL, (void*) &answer, "", NULL, TextBox, "" },
1717 { 0, 0, 0, NULL, (void*) &SendReply, "", NULL, EndMark , "" }
1718 };
1719
1720 int
1721 SendReply (int n)
1722 {
1723     char buf[MSG_SIZ];
1724     int err;
1725     char *reply=answer;
1726 //    GetWidgetText(&askOptions[1], &reply);
1727     safeStrCpy(buf, pendingReplyPrefix, sizeof(buf)/sizeof(buf[0]) );
1728     if (*buf) strncat(buf, " ", MSG_SIZ - strlen(buf) - 1);
1729     strncat(buf, reply, MSG_SIZ - strlen(buf) - 1);
1730     strncat(buf, "\n",  MSG_SIZ - strlen(buf) - 1);
1731     OutputToProcess(pendingReplyPR, buf, strlen(buf), &err); // does not go into debug file??? => bug
1732     if (err) DisplayFatalError(_("Error writing to chess program"), err, 0);
1733     return TRUE;
1734 }
1735
1736 void
1737 AskQuestion (char *title, char *question, char *replyPrefix, ProcRef pr)
1738 {
1739     safeStrCpy(pendingReplyPrefix, replyPrefix, sizeof(pendingReplyPrefix)/sizeof(pendingReplyPrefix[0]) );
1740     pendingReplyPR = pr;
1741     ASSIGN(answer, "");
1742     askOptions[0].name = question;
1743     if(GenericPopUp(askOptions, title, AskDlg, BoardWindow, MODAL, 0))
1744         AddHandler(&askOptions[1], AskDlg, 2);
1745 }
1746
1747 //---------------------------- Promotion Popup --------------------------------------
1748
1749 static int count;
1750
1751 static void PromoPick P((int n));
1752
1753 static Option promoOptions[] = {
1754 {   0,         0,    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,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1760 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1761 {   0,  SAME_ROW,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1762 {   0, SAME_ROW | NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
1763 };
1764
1765 static void
1766 PromoPick (int n)
1767 {
1768     int promoChar = promoOptions[n+count].value;
1769
1770     PopDown(PromoDlg);
1771
1772     if (promoChar == 0) fromX = -1;
1773     if (fromX == -1) return;
1774
1775     if (! promoChar) {
1776         fromX = fromY = -1;
1777         ClearHighlights();
1778         return;
1779     }
1780     if(promoChar == '=' && !IS_SHOGI(gameInfo.variant)) promoChar = NULLCHAR;
1781     UserMoveEvent(fromX, fromY, toX, toY, promoChar);
1782
1783     if (!appData.highlightLastMove || gotPremove) ClearHighlights();
1784     if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
1785     fromX = fromY = -1;
1786 }
1787
1788 static void
1789 SetPromo (char *name, int nr, char promoChar)
1790 {
1791     ASSIGN(promoOptions[nr].name, name);
1792     promoOptions[nr].value = promoChar;
1793     promoOptions[nr].min = SAME_ROW;
1794 }
1795
1796 void
1797 PromotionPopUp (char choice)
1798 { // choice depends on variant: prepare dialog acordingly
1799   count = 8;
1800   SetPromo(_("Cancel"), --count, -1); // Beware: GenericPopUp cannot handle user buttons named "cancel" (lowe case)!
1801   if(choice != '+' && !IS_SHOGI(gameInfo.variant)) {
1802     if (!appData.testLegality || gameInfo.variant == VariantSuicide ||
1803         gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove) ||
1804         gameInfo.variant == VariantGiveaway) {
1805       SetPromo(_("King"), --count, 'k');
1806     }
1807     if(gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove)) {
1808       SetPromo(_("Captain"), --count, 'c');
1809       SetPromo(_("Lieutenant"), --count, 'l');
1810       SetPromo(_("General"), --count, 'g');
1811       SetPromo(_("Warlord"), --count, 'w');
1812     } else {
1813       SetPromo(_("Knight"), --count, 'n');
1814       SetPromo(_("Bishop"), --count, 'b');
1815       SetPromo(_("Rook"), --count, 'r');
1816       if(gameInfo.variant == VariantCapablanca ||
1817          gameInfo.variant == VariantGothic ||
1818          gameInfo.variant == VariantCapaRandom) {
1819         SetPromo(_("Archbishop"), --count, 'a');
1820         SetPromo(_("Chancellor"), --count, 'c');
1821       }
1822       SetPromo(_("Queen"), --count, 'q');
1823       if(gameInfo.variant == VariantChuChess)
1824         SetPromo(_("Lion"), --count, 'l');
1825     }
1826   } else // [HGM] shogi
1827   {
1828       SetPromo(_("Defer"), --count, '=');
1829       SetPromo(_("Promote"), --count, '+');
1830   }
1831   promoOptions[count].min = 0;
1832   GenericPopUp(promoOptions + count, "Promotion", PromoDlg, BoardWindow, NONMODAL, 0);
1833 }
1834
1835 //---------------------------- Chat Windows ----------------------------------------------
1836
1837 static char *line, *memo, *chatMemo, *partner, *texts[MAX_CHAT], dirty[MAX_CHAT], *inputs[MAX_CHAT], *icsLine, *tmpLine;
1838 static int activePartner;
1839 int hidden = 1;
1840
1841 void ChatSwitch P((int n));
1842 int  ChatOK P((int n));
1843
1844 #define CHAT_ICS     6
1845 #define CHAT_PARTNER 8
1846 #define CHAT_OUT    11
1847 #define CHAT_PANE   12
1848 #define CHAT_IN     13
1849
1850 void PaneSwitch P((void));
1851 void ClearChat P((void));
1852
1853 WindowPlacement wpTextMenu;
1854
1855 int
1856 ContextMenu (Option *opt, int button, int x, int y, char *text, int index)
1857 { // callback for ICS-output clicks; handles button 3, passes on other events
1858   int h;
1859   if(button == -3) return TRUE; // supress default GTK context menu on up-click
1860   if(button != 3) return FALSE;
1861   if(index == -1) { // pre-existing selection in memo
1862     strncpy(clickedWord, text, MSG_SIZ);
1863   } else { // figure out what word was clicked
1864     char *start, *end;
1865     start = end = text + index;
1866     while(isalnum(*end)) end++;
1867     while(start > text && isalnum(start[-1])) start--;
1868     clickedWord[0] = NULLCHAR;
1869     if(end-start >= 80) end = start + 80; // intended for small words and numbers
1870     strncpy(clickedWord, start, end-start); clickedWord[end-start] = NULLCHAR;
1871   }
1872   click = !shellUp[TextMenuDlg]; // request auto-popdown of textmenu when we popped it up
1873   h = wpTextMenu.height; // remembered height of text menu
1874   if(h <= 0) h = 65;     // when not available, position w.r.t. top
1875   GetPlacement(ChatDlg, &wpTextMenu);
1876   if(opt->target == (void*) &chatMemo) wpTextMenu.y += (wpTextMenu.height - 30)/2; // click in chat
1877   wpTextMenu.x += x - 50; wpTextMenu.y += y - h + 50; // request positioning
1878   if(wpTextMenu.x < 0) wpTextMenu.x = 0;
1879   if(wpTextMenu.y < 0) wpTextMenu.y = 0;
1880   wpTextMenu.width = wpTextMenu.height = -1;
1881   IcsTextPopUp();
1882   return TRUE;
1883 }
1884
1885 Option chatOptions[] = {
1886 {  0,  0,   0, NULL, NULL, NULL, NULL, Label , N_("Chats:") },
1887 { 1, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
1888 { 2, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
1889 { 3, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
1890 { 4, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
1891 { 5, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
1892 { 250, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &memo, NULL, (void*) &ContextMenu, TextBox, "" },
1893 {  0,  0,   0, NULL, NULL, "", NULL, Break , "" },
1894 { 0,   T_TOP,    100, NULL, (void*) &partner, NULL, NULL, TextBox, N_("Chat partner:") },
1895 {  0, SAME_ROW, 0, NULL, (void*) &ClearChat,  NULL, NULL, Button, N_("End Chat") },
1896 {  0, SAME_ROW, 0, NULL, (void*) &PaneSwitch, NULL, NULL, Button, N_("Hide") },
1897 { 250, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &chatMemo, NULL, (void*) &ContextMenu, TextBox, "" },
1898 {  0,  0,   0, NULL, NULL, "", NULL, Break , "" },
1899 {  0,    0,  510, NULL, (void*) &line, NULL, NULL, TextBox, "" },
1900 { 0, NO_OK|SAME_ROW, 0, NULL, (void*) &ChatOK, NULL, NULL, EndMark , "" }
1901 };
1902
1903 static void
1904 PutText (char *text, int pos)
1905 {
1906     char buf[MSG_SIZ], *p;
1907     DialogClass dlg = ChatDlg;
1908     Option *opt = &chatOptions[CHAT_IN];
1909
1910     if(strstr(text, "$add ") == text) {
1911         GetWidgetText(&boxOptions[INPUT], &p);
1912         snprintf(buf, MSG_SIZ, "%s%s", p, text+5); text = buf;
1913         pos += strlen(p) - 5;
1914     }
1915     if(shellUp[InputBoxDlg]) opt = &boxOptions[INPUT], dlg = InputBoxDlg; // for the benefit of Xaw give priority to ICS Input Box
1916     SetWidgetText(opt, text, dlg);
1917     SetInsertPos(opt, pos);
1918     HardSetFocus(opt, dlg);
1919     CursorAtEnd(opt);
1920 }
1921
1922 int
1923 IcsHist (int n, Option *opt, DialogClass dlg)
1924 {   // [HGM] input: let up-arrow recall previous line from history
1925     char *val = NULL; // to suppress spurious warning
1926     int chat, start;
1927
1928     if(opt != &chatOptions[CHAT_IN] && !(opt == &chatOptions[CHAT_PARTNER] && n == 33)) return 0;
1929     switch(n) {
1930       case 5:
1931         if(!hidden) ClearChat();
1932         break;
1933       case 8:
1934         if(!hidden) PaneSwitch();
1935         break;
1936       case 33: // <Esc>
1937         if(1) BoardToTop(); else
1938         if(hidden) BoardToTop();
1939         else PaneSwitch();
1940         break;
1941       case 15:
1942         NewChat(lastTalker);
1943         break;
1944       case 14:
1945         for(chat=0; chat < MAX_CHAT; chat++) if(!chatPartner[chat][0]) break;
1946         if(chat < MAX_CHAT) ChatSwitch(chat + 1);
1947         break;
1948       case 10: // <Tab>
1949         chat = start = (activePartner - hidden + MAX_CHAT) % MAX_CHAT;
1950         while(!dirty[chat = (chat + 1)%MAX_CHAT]) if(chat == start) break;
1951         if(!dirty[chat])
1952         while(!chatPartner[chat = (chat + 1)%MAX_CHAT][0]) if(chat == start) break;
1953         if(!chatPartner[chat][0]) break; // if all unused, ignore
1954         ChatSwitch(chat + 1);
1955         break;
1956       case 1:
1957         GetWidgetText(opt, &val);
1958         val = PrevInHistory(val);
1959         break;
1960       case -1:
1961         val = NextInHistory();
1962     }
1963     SetWidgetText(opt, val = val ? val : "", dlg);
1964     SetInsertPos(opt, strlen(val));
1965     return 1;
1966 }
1967
1968 void
1969 OutputChatMessage (int partner, char *mess)
1970 {
1971     char *p = texts[partner];
1972     int len = strlen(mess) + 1;
1973
1974     if(!DialogExists(ChatDlg)) return;
1975     if(p) len += strlen(p);
1976     texts[partner] = (char*) malloc(len);
1977     snprintf(texts[partner], len, "%s%s", p ? p : "", mess);
1978     FREE(p);
1979     if(partner == activePartner && !hidden) {
1980         AppendText(&chatOptions[CHAT_OUT], mess);
1981         SetInsertPos(&chatOptions[CHAT_OUT], len-2);
1982     } else {
1983         SetColor("#FFC000", &chatOptions[partner + 1]);
1984         dirty[partner] = 1;
1985     }
1986 }
1987
1988 int
1989 ChatOK (int n)
1990 {   // can only be called through <Enter> in chat-partner text-edit, as there is no OK button
1991     char buf[MSG_SIZ];
1992
1993     if(!hidden && (!partner || strcmp(partner, chatPartner[activePartner]) || !*partner)) {
1994         safeStrCpy(chatPartner[activePartner], partner, MSG_SIZ);
1995         SetWidgetText(&chatOptions[CHAT_OUT], "", -1); // clear text if we alter partner
1996         SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg); // clear text if we alter partner
1997         SetWidgetLabel(&chatOptions[activePartner+1], chatPartner[activePartner][0] ? chatPartner[activePartner] : _("New Chat"));
1998         if(!*partner) PaneSwitch();
1999         HardSetFocus(&chatOptions[CHAT_IN], 0);
2000     }
2001     if(line[0] || hidden) { // something was typed (for ICS commands we also allow empty line!)
2002         SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg);
2003         // from here on it could be back-end
2004         if(line[strlen(line)-1] == '\n') line[strlen(line)-1] = NULLCHAR;
2005         SaveInHistory(line);
2006         if(hidden || !*chatPartner[activePartner]) snprintf(buf, MSG_SIZ, "%s\n", line); else // command for ICS
2007         if(!strcmp("whispers", chatPartner[activePartner]))
2008               snprintf(buf, MSG_SIZ, "whisper %s\n", line); // WHISPER box uses "whisper" to send
2009         else if(!strcmp("shouts", chatPartner[activePartner]))
2010               snprintf(buf, MSG_SIZ, "shout %s\n", line); // SHOUT box uses "shout" to send
2011         else if(!strcmp("c-shouts", chatPartner[activePartner]))
2012               snprintf(buf, MSG_SIZ, "cshout %s\n", line); // C-SHOUT box uses "cshout" to send
2013         else if(!strcmp("kibitzes", chatPartner[activePartner]))
2014               snprintf(buf, MSG_SIZ, "kibitz %s\n", line); // KIBITZ box uses "kibitz" to send
2015         else {
2016             if(!atoi(chatPartner[activePartner])) {
2017                 snprintf(buf, MSG_SIZ, "> %s\n", line); // echo only tells to handle, not channel
2018                 OutputChatMessage(activePartner, buf);
2019                 snprintf(buf, MSG_SIZ, "xtell %s %s\n", chatPartner[activePartner], line);
2020             } else
2021                 snprintf(buf, MSG_SIZ, "tell %s %s\n", chatPartner[activePartner], line);
2022         }
2023         SendToICS(buf);
2024     }
2025     return FALSE; // never pop down
2026 }
2027
2028 void
2029 DelayedSetText ()
2030 {
2031     SetWidgetText(&chatOptions[CHAT_IN], tmpLine, -1); // leave focus on chat-partner field!
2032     SetInsertPos(&chatOptions[CHAT_IN], strlen(tmpLine));
2033 }
2034
2035 void
2036 DelayedScroll ()
2037 {   // If we do this immediately it does it before shrinking the memo, so the lower half remains hidden (Ughh!)
2038     SetInsertPos(&chatOptions[CHAT_ICS], 999999);
2039     SetWidgetText(&chatOptions[CHAT_IN], tmpLine, ChatDlg);
2040     SetInsertPos(&chatOptions[CHAT_IN], strlen(tmpLine));
2041 }
2042
2043 void
2044 ChatSwitch (int n)
2045 {
2046     int i, j;
2047     char *v;
2048     if(chatOptions[CHAT_ICS].type == Skip) hidden = 0; // In Xaw there is no ICS pane we can hide behind
2049     Show(&chatOptions[CHAT_PANE], 0); // show
2050     if(hidden) ScheduleDelayedEvent(DelayedScroll, 50); // Awful!
2051     else ScheduleDelayedEvent(DelayedSetText, 50);
2052     GetWidgetText(&chatOptions[CHAT_IN], &v);
2053     if(hidden) { ASSIGN(icsLine, v); } else { ASSIGN(inputs[activePartner], v); }
2054     hidden = 0;
2055     activePartner = --n;
2056     if(!texts[n]) texts[n] = strdup("");
2057     dirty[n] = 0;
2058     SetWidgetText(&chatOptions[CHAT_OUT], texts[n], ChatDlg);
2059     SetInsertPos(&chatOptions[CHAT_OUT], strlen(texts[n]));
2060     SetWidgetText(&chatOptions[CHAT_PARTNER], chatPartner[n], ChatDlg);
2061     for(i=j=0; i<MAX_CHAT; i++) {
2062         SetWidgetLabel(&chatOptions[++j], *chatPartner[i] ? chatPartner[i] : _("New Chat"));
2063         SetColor(dirty[i] ? "#FFC000" : "#FFFFFF", &chatOptions[j]);
2064     }
2065     if(!inputs[n]) { ASSIGN(inputs[n], ""); }
2066 //    SetWidgetText(&chatOptions[CHAT_IN], inputs[n], ChatDlg); // does not work (in this widget only)
2067 //    SetInsertPos(&chatOptions[CHAT_IN], strlen(inputs[n]));
2068     tmpLine = inputs[n]; // for the delayed event
2069     HardSetFocus(&chatOptions[strcmp(chatPartner[n], "") ? CHAT_IN : CHAT_PARTNER], 0);
2070 }
2071
2072 void
2073 PaneSwitch ()
2074 {
2075     char *v;
2076     Show(&chatOptions[CHAT_PANE], hidden = 1); // hide
2077     GetWidgetText(&chatOptions[CHAT_IN], &v);
2078     ASSIGN(inputs[activePartner], v);
2079     if(!icsLine) { ASSIGN(icsLine, ""); }
2080     tmpLine = icsLine; ScheduleDelayedEvent(DelayedSetText, 50);
2081 //    SetWidgetText(&chatOptions[CHAT_IN], icsLine, ChatDlg); // does not work (in this widget only)
2082 //    SetInsertPos(&chatOptions[CHAT_IN], strlen(icsLine));
2083 }
2084
2085 void
2086 ClearChat ()
2087 {   // clear the chat to make it free for other use
2088     chatPartner[activePartner][0] = NULLCHAR;
2089     ASSIGN(texts[activePartner], "");
2090     ASSIGN(inputs[activePartner], "");
2091     SetWidgetText(&chatOptions[CHAT_PARTNER], "", ChatDlg);
2092     SetWidgetText(&chatOptions[CHAT_OUT], "", ChatDlg);
2093     SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg);
2094     SetWidgetLabel(&chatOptions[activePartner+1], _("New Chat"));
2095     HardSetFocus(&chatOptions[CHAT_PARTNER], 0);
2096 }
2097
2098 static void
2099 NewChat (char *name)
2100 {   // open a chat on program request. If no empty one available, use last
2101     int i;
2102     for(i=0; i<MAX_CHAT-1; i++) if(!chatPartner[i][0]) break;
2103     safeStrCpy(chatPartner[i], name, MSG_SIZ);
2104     ChatSwitch(i+1);
2105 }
2106
2107 void
2108 ConsoleWrite(char *message, int count)
2109 {
2110     if(shellUp[ChatDlg] && chatOptions[CHAT_ICS].type != Skip) { // in Xaw this is a no-op
2111         AppendColorized(&chatOptions[CHAT_ICS], message, count);
2112         SetInsertPos(&chatOptions[CHAT_ICS], 999999);
2113     }
2114 }
2115
2116 void
2117 ChatPopUp ()
2118 {
2119     if(GenericPopUp(chatOptions, _("ICS Interaction"), ChatDlg, BoardWindow, NONMODAL, appData.topLevel))
2120         AddHandler(&chatOptions[CHAT_PARTNER], ChatDlg, 2), AddHandler(&chatOptions[CHAT_IN], ChatDlg, 2); // treats return as OK
2121     Show(&chatOptions[CHAT_PANE], hidden = 1); // hide
2122 //    HardSetFocus(&chatOptions[CHAT_IN], 0);
2123     MarkMenu("View.OpenChatWindow", ChatDlg);
2124     CursorAtEnd(&chatOptions[CHAT_IN]);
2125 }
2126
2127 void
2128 ChatProc ()
2129 {
2130     if(shellUp[ChatDlg]) PopDown(ChatDlg);
2131     else ChatPopUp();
2132 }
2133
2134 void
2135 ConsoleAutoPopUp (char *buf)
2136 {
2137         if(*buf == 27) { if(appData.icsActive && DialogExists(ChatDlg)) HardSetFocus (&chatOptions[CHAT_IN], ChatDlg); return; }
2138         if(!appData.autoBox) return;
2139         if(appData.icsActive) { // text typed to board in ICS mode: divert to ICS input box
2140             if(DialogExists(ChatDlg)) { // box already exists: append to current contents
2141                 char *p, newText[MSG_SIZ];
2142                 GetWidgetText(&chatOptions[CHAT_IN], &p);
2143                 snprintf(newText, MSG_SIZ, "%s%c", p, *buf);
2144                 SetWidgetText(&chatOptions[CHAT_IN], newText, ChatDlg);
2145                 if(shellUp[ChatDlg]) HardSetFocus (&chatOptions[CHAT_IN], ChatDlg); //why???
2146             } else { ASSIGN(line, buf); } // box did not exist: make sure it pops up with char in it
2147             ChatPopUp();
2148         } else PopUpMoveDialog(*buf);
2149 }
2150
2151 //--------------------------------- Game-List options dialog ------------------------------------------
2152
2153 char *strings[LPUSERGLT_SIZE];
2154 int stringPtr;
2155
2156 void
2157 GLT_ClearList ()
2158 {
2159     strings[0] = NULL;
2160     stringPtr = 0;
2161 }
2162
2163 void
2164 GLT_AddToList (char *name)
2165 {
2166     strings[stringPtr++] = name;
2167     strings[stringPtr] = NULL;
2168 }
2169
2170 Boolean
2171 GLT_GetFromList (int index, char *name)
2172 {
2173   safeStrCpy(name, strings[index], MSG_SIZ);
2174   return TRUE;
2175 }
2176
2177 void
2178 GLT_DeSelectList ()
2179 {
2180 }
2181
2182 static void GLT_Button P((int n));
2183 static int GLT_OK P((int n));
2184
2185 static Option listOptions[] = {
2186 {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
2187 { 0,    0,     0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("factory") },
2188 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("up") },
2189 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("down") },
2190 { 0, SAME_ROW, 0, NULL, (void*) &GLT_OK, "", NULL, EndMark , "" }
2191 };
2192
2193 static int
2194 GLT_OK (int n)
2195 {
2196     GLT_ParseList();
2197     appData.gameListTags = strdup(lpUserGLT);
2198     GameListUpdate();
2199     return 1;
2200 }
2201
2202 static void
2203 GLT_Button (int n)
2204 {
2205     int index = SelectedListBoxItem (&listOptions[0]);
2206     char *p;
2207     if (index < 0) {
2208         DisplayError(_("No tag selected"), 0);
2209         return;
2210     }
2211     p = strings[index];
2212     if (n == 3) {
2213         if(index >= strlen(GLT_ALL_TAGS)) return;
2214         strings[index] = strings[index+1];
2215         strings[++index] = p;
2216         LoadListBox(&listOptions[0], "?", index, index-1); // only change the two specified entries
2217     } else
2218     if (n == 2) {
2219         if(index == 0) return;
2220         strings[index] = strings[index-1];
2221         strings[--index] = p;
2222         LoadListBox(&listOptions[0], "?", index, index+1);
2223     } else
2224     if (n == 1) {
2225       safeStrCpy(lpUserGLT, GLT_DEFAULT_TAGS, LPUSERGLT_SIZE);
2226       GLT_TagsToList(lpUserGLT);
2227       index = 0;
2228       LoadListBox(&listOptions[0], "?", -1, -1);
2229     }
2230     HighlightListBoxItem(&listOptions[0], index);
2231 }
2232
2233 void
2234 GameListOptionsPopUp (DialogClass parent)
2235 {
2236     safeStrCpy(lpUserGLT, appData.gameListTags, LPUSERGLT_SIZE);
2237     GLT_TagsToList(lpUserGLT);
2238
2239     GenericPopUp(listOptions, _("Game-list options"), TransientDlg, parent, MODAL, 0);
2240 }
2241
2242 void
2243 GameListOptionsProc ()
2244 {
2245     GameListOptionsPopUp(BoardWindow);
2246 }
2247
2248 //----------------------------- Error popup in various uses -----------------------------
2249
2250 /*
2251  * [HGM] Note:
2252  * XBoard has always had some pathologic behavior with multiple simultaneous error popups,
2253  * (which can occur even for modal popups when asynchrounous events, e.g. caused by engine, request a popup),
2254  * and this new implementation reproduces that as well:
2255  * Only the shell of the last instance is remembered in shells[ErrorDlg] (which replaces errorShell),
2256  * so that PopDowns ordered from the code always refer to that instance, and once that is down,
2257  * have no clue as to how to reach the others. For the Delete Window button calling PopDown this
2258  * has now been repaired, as the action routine assigned to it gets the shell passed as argument.
2259  */
2260
2261 int errorUp = False;
2262
2263 void
2264 ErrorPopDown ()
2265 {
2266     if (!errorUp) return;
2267     dialogError = errorUp = False;
2268     PopDown(ErrorDlg); PopDown(FatalDlg); // on explicit request we pop down any error dialog
2269     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
2270 }
2271
2272 static int
2273 ErrorOK (int n)
2274 {
2275     dialogError = errorUp = False;
2276     PopDown(n == 1 ? FatalDlg : ErrorDlg); // kludge: non-modal dialogs have one less (dummy) option
2277     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
2278     return FALSE; // prevent second Popdown !
2279 }
2280
2281 static Option errorOptions[] = {
2282 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // dummy option: will never be displayed
2283 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // textValue field will be set before popup
2284 { 0,NO_CANCEL,0, NULL, (void*) &ErrorOK, "", NULL, EndMark , "" }
2285 };
2286
2287 void
2288 ErrorPopUp (char *title, char *label, int modal)
2289 {
2290     errorUp = True;
2291     errorOptions[1].name = label;
2292     if(dialogError = shellUp[MasterDlg])
2293         GenericPopUp(errorOptions+1, title, FatalDlg, MasterDlg, MODAL, 0); // pop up as daughter of the transient dialog
2294     else
2295         GenericPopUp(errorOptions+modal, title, modal ? FatalDlg: ErrorDlg, BoardWindow, modal, 0); // kludge: option start address indicates modality
2296 }
2297
2298 void
2299 DisplayError (String message, int error)
2300 {
2301     char buf[MSG_SIZ];
2302
2303     if (error == 0) {
2304         if (appData.debugMode || appData.matchMode) {
2305             fprintf(stderr, "%s: %s\n", programName, message);
2306         }
2307     } else {
2308         if (appData.debugMode || appData.matchMode) {
2309             fprintf(stderr, "%s: %s: %s\n",
2310                     programName, message, strerror(error));
2311         }
2312         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
2313         message = buf;
2314     }
2315     ErrorPopUp(_("Error"), message, FALSE);
2316 }
2317
2318
2319 void
2320 DisplayMoveError (String message)
2321 {
2322     fromX = fromY = -1;
2323     ClearHighlights();
2324     DrawPosition(TRUE, NULL); // selective redraw would miss the from-square of the rejected move, displayed empty after drag, but not marked damaged!
2325     if (appData.debugMode || appData.matchMode) {
2326         fprintf(stderr, "%s: %s\n", programName, message);
2327     }
2328     if (appData.popupMoveErrors) {
2329         ErrorPopUp(_("Error"), message, FALSE);
2330     } else {
2331         DisplayMessage(message, "");
2332     }
2333 }
2334
2335
2336 void
2337 DisplayFatalError (String message, int error, int status)
2338 {
2339     char buf[MSG_SIZ];
2340
2341     errorExitStatus = status;
2342     if (error == 0) {
2343         fprintf(stderr, "%s: %s\n", programName, message);
2344     } else {
2345         fprintf(stderr, "%s: %s: %s\n",
2346                 programName, message, strerror(error));
2347         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
2348         message = buf;
2349     }
2350     if(mainOptions[W_BOARD].handle) {
2351         if (appData.popupExitMessage) {
2352             ErrorPopUp(status ? _("Fatal Error") : _("Exiting"), message, TRUE);
2353         } else {
2354             ExitEvent(status);
2355         }
2356     }
2357 }
2358
2359 void
2360 DisplayInformation (String message)
2361 {
2362     ErrorPopDown();
2363     ErrorPopUp(_("Information"), message, TRUE);
2364 }
2365
2366 void
2367 DisplayNote (String message)
2368 {
2369     ErrorPopDown();
2370     ErrorPopUp(_("Note"), message, FALSE);
2371 }
2372
2373 void
2374 DisplayTitle (char *text)
2375 {
2376     char title[MSG_SIZ];
2377     char icon[MSG_SIZ];
2378
2379     if (text == NULL) text = "";
2380
2381     if(partnerUp) { SetDialogTitle(DummyDlg, text); return; }
2382
2383     if (*text != NULLCHAR) {
2384       safeStrCpy(icon, text, sizeof(icon)/sizeof(icon[0]) );
2385       safeStrCpy(title, text, sizeof(title)/sizeof(title[0]) );
2386     } else if (appData.icsActive) {
2387         snprintf(icon, sizeof(icon), "%s", appData.icsHost);
2388         snprintf(title, sizeof(title), "%s: %s", programName, appData.icsHost);
2389     } else if (appData.cmailGameName[0] != NULLCHAR) {
2390         snprintf(icon, sizeof(icon), "%s", "CMail");
2391         snprintf(title,sizeof(title), "%s: %s", programName, "CMail");
2392 #ifdef GOTHIC
2393     // [HGM] license: This stuff should really be done in back-end, but WinBoard already had a pop-up for it
2394     } else if (gameInfo.variant == VariantGothic) {
2395       safeStrCpy(icon,  programName, sizeof(icon)/sizeof(icon[0]) );
2396       safeStrCpy(title, GOTHIC,     sizeof(title)/sizeof(title[0]) );
2397 #endif
2398 #ifdef FALCON
2399     } else if (gameInfo.variant == VariantFalcon) {
2400       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
2401       safeStrCpy(title, FALCON, sizeof(title)/sizeof(title[0]) );
2402 #endif
2403     } else if (appData.noChessProgram) {
2404       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
2405       safeStrCpy(title, programName, sizeof(title)/sizeof(title[0]) );
2406     } else {
2407       safeStrCpy(icon, first.tidy, sizeof(icon)/sizeof(icon[0]) );
2408         snprintf(title,sizeof(title), "%s: %s", programName, first.tidy);
2409     }
2410     SetWindowTitle(text, title, icon);
2411 }
2412
2413 #define PAUSE_BUTTON "P"
2414 #define PIECE_MENU_SIZE 18
2415 static String pieceMenuStrings[2][PIECE_MENU_SIZE+1] = {
2416     { N_("White"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
2417       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
2418       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
2419       N_("Empty square"), N_("Clear board"), NULL },
2420     { N_("Black"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
2421       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
2422       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
2423       N_("Empty square"), N_("Clear board"), NULL }
2424 };
2425 /* must be in same order as pieceMenuStrings! */
2426 static ChessSquare pieceMenuTranslation[2][PIECE_MENU_SIZE] = {
2427     { WhitePlay, (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2428         WhiteRook, WhiteQueen, WhiteKing, (ChessSquare) 0, WhiteAlfil,
2429         WhiteCannon, WhiteAngel, WhiteMarshall, (ChessSquare) 0,
2430         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2431     { BlackPlay, (ChessSquare) 0, BlackPawn, BlackKnight, BlackBishop,
2432         BlackRook, BlackQueen, BlackKing, (ChessSquare) 0, BlackAlfil,
2433         BlackCannon, BlackAngel, BlackMarshall, (ChessSquare) 0,
2434         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2435 };
2436
2437 #define DROP_MENU_SIZE 6
2438 static String dropMenuStrings[DROP_MENU_SIZE+1] = {
2439     "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"), N_("Queen"), NULL
2440   };
2441 /* must be in same order as dropMenuStrings! */
2442 static ChessSquare dropMenuTranslation[DROP_MENU_SIZE] = {
2443     (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2444     WhiteRook, WhiteQueen
2445 };
2446
2447 // [HGM] experimental code to pop up window just like the main window, using GenercicPopUp
2448
2449 static Option *Exp P((int n, int x, int y));
2450 void MenuCallback P((int n));
2451 void SizeKludge P((int n));
2452 static Option *LogoW P((int n, int x, int y));
2453 static Option *LogoB P((int n, int x, int y));
2454
2455 static int pmFromX = -1, pmFromY = -1;
2456 void *userLogo;
2457
2458 void
2459 DisplayLogos (Option *w1, Option *w2)
2460 {
2461         void *whiteLogo = first.programLogo, *blackLogo = second.programLogo;
2462         if(appData.autoLogo) {
2463           if(appData.noChessProgram) whiteLogo = blackLogo = NULL;
2464           if(appData.icsActive) whiteLogo = blackLogo = second.programLogo;
2465           switch(gameMode) { // pick logos based on game mode
2466             case IcsObserving:
2467                 whiteLogo = second.programLogo; // ICS logo
2468                 blackLogo = second.programLogo;
2469             default:
2470                 break;
2471             case IcsPlayingWhite:
2472                 if(!appData.zippyPlay) whiteLogo = userLogo;
2473                 blackLogo = second.programLogo; // ICS logo
2474                 break;
2475             case IcsPlayingBlack:
2476                 whiteLogo = second.programLogo; // ICS logo
2477                 blackLogo = appData.zippyPlay ? first.programLogo : userLogo;
2478                 break;
2479             case TwoMachinesPlay:
2480                 if(first.twoMachinesColor[0] == 'b') {
2481                     whiteLogo = second.programLogo;
2482                     blackLogo = first.programLogo;
2483                 }
2484                 break;
2485             case MachinePlaysWhite:
2486                 blackLogo = userLogo;
2487                 break;
2488             case MachinePlaysBlack:
2489                 whiteLogo = userLogo;
2490                 blackLogo = first.programLogo;
2491           }
2492         }
2493         DrawLogo(w1, whiteLogo);
2494         DrawLogo(w2, blackLogo);
2495 }
2496
2497 static void
2498 PMSelect (int n)
2499 {   // user callback for board context menus
2500     if (pmFromX < 0 || pmFromY < 0) return;
2501     if(n == W_DROP) DropMenuEvent(dropMenuTranslation[values[n]], pmFromX, pmFromY);
2502     else EditPositionMenuEvent(pieceMenuTranslation[n - W_MENUW][values[n]], pmFromX, pmFromY);
2503 }
2504
2505 static void
2506 CCB (int n)
2507 {
2508     shiftKey = (ShiftKeys() & 3) != 0;
2509     if(n < 0) { // button != 1
2510         n = -n;
2511         if(shiftKey && (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack)) {
2512             AdjustClock(n == W_BLACK, 1);
2513         }
2514     } else
2515     ClockClick(n == W_BLACK);
2516 }
2517
2518 Option mainOptions[] = { // description of main window in terms of generic dialog creator
2519 { 0, 0xCA, 0, NULL, NULL, "", NULL, BarBegin, "" }, // menu bar
2520   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_File") },
2521   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Edit") },
2522   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_View") },
2523   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Mode") },
2524   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Action") },
2525   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("E_ngine") },
2526   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Options") },
2527   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("_Help") },
2528 { 0, 0, 0, NULL, (void*)&SizeKludge, "", NULL, BarEnd, "" },
2529 { 0, LR|T2T|BORDER|SAME_ROW, 0, NULL, NULL, NULL, NULL, Label, "1" }, // optional title in window
2530 { 50,    LL|TT,            100, NULL, (void*) &LogoW, NULL, NULL, Skip, "" }, // white logo
2531 { 12,   L2L|T2T,           200, NULL, (void*) &CCB, NULL, NULL, Label, "White" }, // white clock
2532 { 13,   R2R|T2T|SAME_ROW,  200, NULL, (void*) &CCB, NULL, NULL, Label, "Black" }, // black clock
2533 { 50,    RR|TT|SAME_ROW,   100, NULL, (void*) &LogoB, NULL, NULL, Skip, "" }, // black logo
2534 { 0, LR|T2T|BORDER,        401, NULL, NULL, "", NULL, Skip, "2" }, // backup for title in window (if no room for other)
2535 { 0, LR|T2T|BORDER,        270, NULL, NULL, NULL, NULL, Label, "message", &appData.font }, // message field
2536 { 0, RR|TT|SAME_ROW,       125, NULL, NULL, "", NULL, BoxBegin, "" }, // (optional) button bar
2537   { 0,    0,     0, NULL, (void*) &ToStartEvent,  NULL, NULL, Button, N_("<<"), &appData.font },
2538   { 0, SAME_ROW, 0, NULL, (void*) &BackwardEvent, NULL, NULL, Button, N_("<"),  &appData.font },
2539   { 0, SAME_ROW, 0, NULL, (void*) &PauseEvent,    NULL, NULL, Button, N_(PAUSE_BUTTON), &appData.font },
2540   { 0, SAME_ROW, 0, NULL, (void*) &ForwardEvent,  NULL, NULL, Button, N_(">"),  &appData.font },
2541   { 0, SAME_ROW, 0, NULL, (void*) &ToEndEvent,    NULL, NULL, Button, N_(">>"), &appData.font },
2542 { 0, 0, 0, NULL, NULL, "", NULL, BoxEnd, "" },
2543 { 401, LR|TB, 401, NULL, (char*) &Exp, NULL, NULL, Graph, "shadow board" }, // board
2544   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[0], PopUp, "menuW" },
2545   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[1], PopUp, "menuB" },
2546   { -1, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, dropMenuStrings, PopUp, "menuD" },
2547 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2548 };
2549
2550 Option *
2551 LogoW (int n, int x, int y)
2552 {
2553     if(n == 10) DisplayLogos(&mainOptions[W_WHITE-1], NULL);
2554     return NULL;
2555 }
2556
2557 Option *
2558 LogoB (int n, int x, int y)
2559 {
2560     if(n == 10) DisplayLogos(NULL, &mainOptions[W_BLACK+1]);
2561     return NULL;
2562 }
2563
2564 void
2565 SizeKludge (int n)
2566 {   // callback called by GenericPopUp immediately after sizing the menu bar
2567     int width = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2568     int w = width - 44 - mainOptions[n].min;
2569     mainOptions[W_TITLE].max = w; // width left behind menu bar
2570     if(w < 0.4*width) // if no reasonable amount of space for title, force small layout
2571         mainOptions[W_SMALL].type = mainOptions[W_TITLE].type, mainOptions[W_TITLE].type = Skip;
2572 }
2573
2574 void
2575 MenuCallback (int n)
2576 {
2577     MenuProc *proc = (MenuProc *) (((MenuItem*)(mainOptions[n].choice))[values[n]].proc);
2578
2579     if(!proc) RecentEngineEvent(values[n] - firstEngineItem); else (proc)();
2580 }
2581
2582 static Option *
2583 Exp (int n, int x, int y)
2584 {
2585     static int but1, but3, oldW, oldH, oldX, oldY;
2586     int menuNr = -3, sizing, f, r;
2587     TimeMark now;
2588     extern Boolean right;
2589
2590     if(right) {  // kludgy way to let button 1 double as button 3 when back-end requests this
2591         if(but1 && n == 0) but1 = 0, but3 = 1;
2592         else if(n == -1) n = -3, right = FALSE;
2593     }
2594
2595     if(n == 0) { // motion
2596         oldX = x; oldY = y;
2597         if(SeekGraphClick(Press, x, y, 1)) return NULL;
2598         if((but1 || dragging == 2) && !PromoScroll(x, y)) DragPieceMove(x, y);
2599         if(but3) MovePV(x, y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
2600         if(appData.highlightDragging) {
2601             f = EventToSquare(x, BOARD_WIDTH);  if ( flipView && f >= 0) f = BOARD_WIDTH - 1 - f;
2602             r = EventToSquare(y, BOARD_HEIGHT); if (!flipView && r >= 0) r = BOARD_HEIGHT - 1 - r;
2603             HoverEvent(x, y, f, r);
2604         }
2605         return NULL;
2606     }
2607     if(n != 10 && PopDown(PromoDlg)) fromX = fromY = -1; // user starts fiddling with board when promotion dialog is up
2608     else GetTimeMark(&now);
2609     shiftKey = ShiftKeys();
2610     controlKey = (shiftKey & 0xC) != 0;
2611     shiftKey = (shiftKey & 3) != 0;
2612     switch(n) {
2613         case  1: LeftClick(Press,   x, y), but1 = 1; break;
2614         case -1: LeftClick(Release, x, y), but1 = 0; break;
2615         case  2: shiftKey = !shiftKey;
2616         case  3: menuNr = RightClick(Press,   x, y, &pmFromX, &pmFromY), but3 = 1; break;
2617         case -2: shiftKey = !shiftKey;
2618         case -3: menuNr = RightClick(Release, x, y, &pmFromX, &pmFromY), but3 = 0; break;
2619         case  4: Wheel(-1, oldX, oldY); break;
2620         case  5: Wheel(1, oldX, oldY); break;
2621         case 10:
2622             sizing = (oldW != x || oldH != y);
2623             oldW = x; oldH = y;
2624             InitDrawingHandle(mainOptions + W_BOARD);
2625             if(sizing && SubtractTimeMarks(&now, &programStartTime) > 10000) return NULL; // don't redraw while sizing (except at startup)
2626             DrawPosition(True, NULL);
2627         default:
2628             return NULL;
2629     }
2630
2631     switch(menuNr) {
2632       case 0: return &mainOptions[shiftKey ? W_MENUW: W_MENUB];
2633       case 1: SetupDropMenu(); return &mainOptions[W_DROP];
2634       case 2:
2635       case -1: ErrorPopDown();
2636       case -2:
2637       default: break; // -3, so no clicks caught
2638     }
2639     return NULL;
2640 }
2641
2642 Option *
2643 BoardPopUp (int squareSize, int lineGap, void *clockFontThingy)
2644 {
2645     int i, size = BOARD_WIDTH*(squareSize + lineGap) + lineGap, logo = appData.logoSize;
2646     int f = 2*appData.fixedSize; // width fudge, needed for unknown reasons to not clip board
2647     mainOptions[W_WHITE].choice = (char**) clockFontThingy;
2648     mainOptions[W_BLACK].choice = (char**) clockFontThingy;
2649     mainOptions[W_BOARD].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2650     mainOptions[W_BOARD].max = mainOptions[W_SMALL].max = size; // board size
2651     mainOptions[W_SMALL].max = size - 2; // board title (subtract border!)
2652     mainOptions[W_BLACK].max = mainOptions[W_WHITE].max = size/2-3; // clock width
2653     mainOptions[W_MESSG].max = appData.showButtonBar ? size-135+f : size-2+f; // message
2654     mainOptions[W_MENU].max = size-40; // menu bar
2655     mainOptions[W_TITLE].type = appData.titleInWindow ? Label : Skip ;
2656     if(logo && logo <= size/4) { // Activate logos
2657         mainOptions[W_WHITE-1].type = mainOptions[W_BLACK+1].type = Graph;
2658         mainOptions[W_WHITE-1].max  = mainOptions[W_BLACK+1].max  = logo;
2659         mainOptions[W_WHITE-1].value= mainOptions[W_BLACK+1].value= logo/2;
2660         mainOptions[W_WHITE].min  |= SAME_ROW;
2661         mainOptions[W_WHITE].max  = mainOptions[W_BLACK].max  -= logo + 4;
2662         mainOptions[W_WHITE].name = mainOptions[W_BLACK].name = "Double\nHeight";
2663     }
2664     if(!appData.showButtonBar) for(i=W_BUTTON; i<W_BOARD; i++) mainOptions[i].type = Skip;
2665     for(i=0; i<8; i++) mainOptions[i+1].choice = (char**) menuBar[i].mi;
2666     AppendEnginesToMenu(appData.recentEngineList);
2667     GenericPopUp(mainOptions, "XBoard", BoardWindow, BoardWindow, NONMODAL, 1); // allways top-level
2668     return mainOptions;
2669 }
2670
2671 static Option *
2672 SlaveExp (int n, int x, int y)
2673 {
2674     if(n == 10) { // expose event
2675         flipView = !flipView; partnerUp = !partnerUp;
2676         DrawPosition(True, NULL); // [HGM] dual: draw other board in other orientation
2677         flipView = !flipView; partnerUp = !partnerUp;
2678     }
2679     return NULL;
2680 }
2681
2682 Option dualOptions[] = { // auxiliary board window
2683 { 0, L2L|T2T,              198, NULL, NULL, NULL, NULL, Label, "White" }, // white clock
2684 { 0, R2R|T2T|SAME_ROW,     198, NULL, NULL, NULL, NULL, Label, "Black" }, // black clock
2685 { 0, LR|T2T|BORDER,        401, NULL, NULL, NULL, NULL, Label, "This feature is experimental" }, // message field
2686 { 401, LR|TT, 401, NULL, (char*) &SlaveExp, NULL, NULL, Graph, "shadow board" }, // board
2687 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2688 };
2689
2690 void
2691 SlavePopUp ()
2692 {
2693     int size = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2694     // copy params from main board
2695     dualOptions[0].choice = mainOptions[W_WHITE].choice;
2696     dualOptions[1].choice = mainOptions[W_BLACK].choice;
2697     dualOptions[3].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2698     dualOptions[3].max = dualOptions[2].max = size; // board width
2699     dualOptions[0].max = dualOptions[1].max = size/2 - 3; // clock width
2700     GenericPopUp(dualOptions, "XBoard", DummyDlg, BoardWindow, NONMODAL, appData.topLevel);
2701     SlaveResize(dualOptions+3);
2702 }
2703
2704 void
2705 DisplayWhiteClock (long timeRemaining, int highlight)
2706 {
2707     if(appData.noGUI) return;
2708     if(twoBoards && partnerUp) {
2709         DisplayTimerLabel(&dualOptions[0], _("White"), timeRemaining, highlight);
2710         return;
2711     }
2712     DisplayTimerLabel(&mainOptions[W_WHITE], _("White"), timeRemaining, highlight);
2713     if(highlight) SetClockIcon(0);
2714 }
2715
2716 void
2717 DisplayBlackClock (long timeRemaining, int highlight)
2718 {
2719     if(appData.noGUI) return;
2720     if(twoBoards && partnerUp) {
2721         DisplayTimerLabel(&dualOptions[1], _("Black"), timeRemaining, highlight);
2722         return;
2723     }
2724     DisplayTimerLabel(&mainOptions[W_BLACK], _("Black"), timeRemaining, highlight);
2725     if(highlight) SetClockIcon(1);
2726 }
2727
2728
2729 //---------------------------------------------
2730
2731 void
2732 DisplayMessage (char *message, char *extMessage)
2733 {
2734   /* display a message in the message widget */
2735
2736   char buf[MSG_SIZ];
2737
2738   if (extMessage)
2739     {
2740       if (*message)
2741         {
2742           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
2743           message = buf;
2744         }
2745       else
2746         {
2747           message = extMessage;
2748         };
2749     };
2750
2751     safeStrCpy(lastMsg, message, MSG_SIZ); // [HGM] make available
2752
2753   /* need to test if messageWidget already exists, since this function
2754      can also be called during the startup, if for example a Xresource
2755      is not set up correctly */
2756   if(mainOptions[W_MESSG].handle)
2757     SetWidgetLabel(&mainOptions[W_MESSG], message);
2758
2759   return;
2760 }
2761
2762 //----------------------------------- File Browser -------------------------------
2763
2764 #ifdef HAVE_DIRENT_H
2765 #include <dirent.h>
2766 #else
2767 #include <sys/dir.h>
2768 #define dirent direct
2769 #endif
2770
2771 #include <sys/stat.h>
2772
2773 #define MAXFILES 1000
2774
2775 static DialogClass savDlg;
2776 static ChessProgramState *savCps;
2777 static FILE **savFP;
2778 static char *fileName, *extFilter, *savMode, **namePtr;
2779 static int folderPtr, filePtr, oldVal, byExtension, extFlag, pageStart, cnt;
2780 static char curDir[MSG_SIZ], title[MSG_SIZ], *folderList[MAXFILES], *fileList[MAXFILES];
2781
2782 static char *FileTypes[] = {
2783 "Chess Games",
2784 "Chess Positions",
2785 "Tournaments",
2786 "Opening Books",
2787 "Sound files",
2788 "Images",
2789 "Settings (*.ini)",
2790 "Log files",
2791 "All files",
2792 NULL,
2793 "PGN",
2794 "Old-Style Games",
2795 "FEN",
2796 "Old-Style Positions",
2797 NULL,
2798 NULL
2799 };
2800
2801 static char *Extensions[] = {
2802 ".pgn .game",
2803 ".fen .epd .pos",
2804 ".trn",
2805 ".bin",
2806 ".wav",
2807 ".ini",
2808 ".log",
2809 "",
2810 "INVALID",
2811 ".pgn",
2812 ".game",
2813 ".fen",
2814 ".pos",
2815 NULL,
2816 ""
2817 };
2818
2819 void DirSelProc P((int n, int sel));
2820 void FileSelProc P((int n, int sel));
2821 void SetTypeFilter P((int n));
2822 int BrowseOK P((int n));
2823 void Switch P((int n));
2824 void CreateDir P((int n));
2825
2826 Option browseOptions[] = {
2827 {   0,    LR|T2T,      500, NULL, NULL, NULL, NULL, Label, title },
2828 {   0,    L2L|T2T,     250, NULL, NULL, NULL, NULL, Label, N_("Directories:") },
2829 {   0,R2R|T2T|SAME_ROW,100, NULL, NULL, NULL, NULL, Label, N_("Files:") },
2830 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by name") },
2831 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by type") },
2832 { 300,    L2L|TB,      250, NULL, (void*) folderList, (char*) &DirSelProc, NULL, ListBox, "" },
2833 { 300, R2R|TB|SAME_ROW,250, NULL, (void*) fileList, (char*) &FileSelProc, NULL, ListBox, "" },
2834 {   0,       0,        300, NULL, (void*) &fileName, NULL, NULL, TextBox, N_("Filename:") },
2835 {   0,    SAME_ROW,    120, NULL, (void*) &CreateDir, NULL, NULL, Button, N_("New directory") },
2836 {   0, COMBO_CALLBACK, 150, NULL, (void*) &SetTypeFilter, NULL, FileTypes, ComboBox, N_("File type:") },
2837 {   0,    SAME_ROW,      0, NULL, (void*) &BrowseOK, "", NULL, EndMark , "" }
2838 };
2839
2840 int
2841 BrowseOK (int n)
2842 {
2843         if(!fileName[0]) { // it is enough to have a file selected
2844             if(browseOptions[6].textValue) { // kludge: if callback specified we browse for file
2845                 int sel = SelectedListBoxItem(&browseOptions[6]);
2846                 if(sel < 0 || sel >= filePtr) return FALSE;
2847                 ASSIGN(fileName, fileList[sel]);
2848             } else { // we browse for path
2849                 ASSIGN(fileName, curDir); // kludge: without callback we browse for path
2850             }
2851         }
2852         if(!fileName[0]) return FALSE; // refuse OK when no file
2853         if(!savMode[0]) { // browsing for name only (dialog Browse button)
2854                 if(fileName[0] == '/') // We already had a path name
2855                     snprintf(title, MSG_SIZ, "%s", fileName);
2856                 else
2857                     snprintf(title, MSG_SIZ, "%s/%s", curDir, fileName);
2858                 SetWidgetText((Option*) savFP, title, savDlg);
2859                 currentCps = savCps; // could return to Engine Settings dialog!
2860                 return TRUE;
2861         }
2862         *savFP = fopen(fileName, savMode);
2863         if(*savFP == NULL) return FALSE; // refuse OK if file not openable
2864         ASSIGN(*namePtr, fileName);
2865         ScheduleDelayedEvent(DelayedLoad, 50);
2866         currentCps = savCps; // not sure this is ever non-null
2867         return TRUE;
2868 }
2869
2870 int
2871 AlphaNumCompare (char *p, char *q)
2872 {
2873     while(*p) {
2874         if(isdigit(*p) && isdigit(*q) && atoi(p) != atoi(q))
2875              return (atoi(p) > atoi(q) ? 1 : -1);
2876         if(*p != *q) break;
2877         p++, q++;
2878     }
2879     if(*p == *q) return 0;
2880     return (*p > *q ? 1 : -1);
2881 }
2882
2883 int
2884 Comp (const void *s, const void *t)
2885 {
2886     char *p = *(char**) s, *q = *(char**) t;
2887     if(extFlag) {
2888         char *h; int r;
2889         while(h = strchr(p, '.')) p = h+1;
2890         if(p == *(char**) s) p = "";
2891         while(h = strchr(q, '.')) q = h+1;
2892         if(q == *(char**) t) q = "";
2893         r = AlphaNumCompare(p, q);
2894         if(r) return r;
2895     }
2896     return AlphaNumCompare( *(char**) s, *(char**) t );
2897 }
2898
2899 void
2900 ListDir (int pathFlag)
2901 {
2902         DIR *dir;
2903         struct dirent *dp;
2904         struct stat statBuf;
2905         static int lastFlag;
2906
2907         if(pathFlag < 0) pathFlag = lastFlag;
2908         lastFlag = pathFlag;
2909         dir = opendir(".");
2910         getcwd(curDir, MSG_SIZ);
2911         snprintf(title, MSG_SIZ, "%s   %s", _("Contents of"), curDir);
2912         folderPtr = filePtr = cnt = 0; // clear listing
2913
2914         while (dp = readdir(dir)) { // pass 1: list foders
2915             char *s = dp->d_name;
2916             if(!stat(s, &statBuf) && S_ISDIR(statBuf.st_mode)) { // stat succeeds and tells us it is directory
2917                 if(s[0] == '.' && strcmp(s, "..")) continue; // suppress hidden, except ".."
2918                 ASSIGN(folderList[folderPtr], s); if(folderPtr < MAXFILES-2) folderPtr++;
2919             } else if(!pathFlag) {
2920                 char *s = dp->d_name, match=0;
2921 //              if(cnt == pageStart) { ASSIGN }
2922                 if(s[0] == '.') continue; // suppress hidden files
2923                 if(extFilter[0]) { // [HGM] filter on extension
2924                     char *p = extFilter, *q;
2925                     do {
2926                         if(q = strchr(p, ' ')) *q = 0;
2927                         if(strstr(s, p)) match++;
2928                         if(q) *q = ' ';
2929                     } while(q && (p = q+1));
2930                     if(!match) continue;
2931                 }
2932                 if(filePtr == MAXFILES-2) continue;
2933                 if(cnt++ < pageStart) continue;
2934                 ASSIGN(fileList[filePtr], s); filePtr++;
2935             }
2936         }
2937         if(filePtr == MAXFILES-2) { ASSIGN(fileList[filePtr], _("  next page")); filePtr++; }
2938         FREE(folderList[folderPtr]); folderList[folderPtr] = NULL;
2939         FREE(fileList[filePtr]); fileList[filePtr] = NULL;
2940         closedir(dir);
2941         extFlag = 0;         qsort((void*)folderList, folderPtr, sizeof(char*), &Comp);
2942         extFlag = byExtension; qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
2943 }
2944
2945 void
2946 Refresh (int pathFlag)
2947 {
2948     ListDir(pathFlag); // and make new one
2949     LoadListBox(&browseOptions[5], "", -1, -1);
2950     LoadListBox(&browseOptions[6], "", -1, -1);
2951     SetWidgetLabel(&browseOptions[0], title);
2952 }
2953
2954 static char msg1[] = N_("FIRST TYPE DIRECTORY NAME HERE");
2955 static char msg2[] = N_("TRY ANOTHER NAME");
2956
2957 void
2958 CreateDir (int n)
2959 {
2960     char *name, *errmsg = "";
2961     GetWidgetText(&browseOptions[n-1], &name);
2962     if(!strcmp(name, msg1) || !strcmp(name, msg2)) return;
2963     if(!name[0]) errmsg = _(msg1); else
2964     if(mkdir(name, 0755)) errmsg = _(msg2);
2965     else {
2966         chdir(name);
2967         Refresh(-1);
2968     }
2969     SetWidgetText(&browseOptions[n-1], errmsg, BrowserDlg);
2970 }
2971
2972 void
2973 Switch (int n)
2974 {
2975     if(byExtension == (n == 4)) return;
2976     extFlag = byExtension = (n == 4);
2977     qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
2978     LoadListBox(&browseOptions[6], "", -1, -1);
2979 }
2980
2981 void
2982 SetTypeFilter (int n)
2983 {
2984     int j = values[n];
2985     if(j == browseOptions[n].value) return; // no change
2986     browseOptions[n].value = j;
2987     SetWidgetLabel(&browseOptions[n], FileTypes[j]);
2988     ASSIGN(extFilter, Extensions[j]);
2989     pageStart = 0;
2990     Refresh(-1); // uses pathflag remembered by ListDir
2991     values[n] = oldVal; // do not disturb combo settings of underlying dialog
2992 }
2993
2994 void
2995 FileSelProc (int n, int sel)
2996 {
2997     if(sel < 0 || fileList[sel] == NULL) return;
2998     if(sel == MAXFILES-2) { pageStart = cnt; Refresh(-1); return; }
2999     ASSIGN(fileName, fileList[sel]);
3000     if(BrowseOK(0)) PopDown(BrowserDlg);
3001 }
3002
3003 void
3004 DirSelProc (int n, int sel)
3005 {
3006     if(!chdir(folderList[sel])) { // cd succeeded, so we are in new directory now
3007         Refresh(-1);
3008     }
3009 }
3010
3011 void
3012 StartDir (char *filter, char *newName)
3013 {
3014     static char *gamesDir, *trnDir, *imgDir, *bookDir;
3015     static char curDir[MSG_SIZ];
3016     char **res = NULL;
3017     if(!filter || !*filter) return;
3018     if(strstr(filter, "pgn")) res = &gamesDir; else
3019     if(strstr(filter, "bin")) res = &bookDir; else
3020     if(strstr(filter, "png")) res = &imgDir; else
3021     if(strstr(filter, "trn")) res = &trnDir; else
3022     if(strstr(filter, "fen")) res = &appData.positionDir;
3023     if(res) {
3024         if(newName) {
3025             char *p, *q;
3026             if(*newName) {
3027                 ASSIGN(*res, newName);
3028                 for(p=*res; q=strchr(p, '/');) p = q + 1; *p = NULLCHAR;
3029             }
3030             if(*curDir) chdir(curDir);
3031             *curDir = NULLCHAR;
3032         } else {
3033             getcwd(curDir, MSG_SIZ);
3034             if(*res && **res) chdir(*res);
3035         }
3036     }
3037 }
3038
3039 void
3040 Browse (DialogClass dlg, char *label, char *proposed, char *ext, Boolean pathFlag, char *mode, char **name, FILE **fp)
3041 {
3042     int j=0;
3043     savFP = fp; savMode = mode, namePtr = name, savCps = currentCps, oldVal = values[9], savDlg = dlg; // save params, for use in callback
3044     ASSIGN(extFilter, ext);
3045     ASSIGN(fileName, proposed ? proposed : "");
3046     for(j=0; Extensions[j]; j++) // look up actual value in list of possible values, to get selection nr
3047         if(extFilter && !strcmp(extFilter, Extensions[j])) break;
3048     if(Extensions[j] == NULL) { j++; ASSIGN(FileTypes[j], extFilter); }
3049     browseOptions[9].value = j;
3050     browseOptions[6].textValue = (char*) (pathFlag ? NULL : &FileSelProc); // disable file listbox during path browsing
3051     pageStart = 0; ListDir(pathFlag);
3052     currentCps = NULL;
3053     GenericPopUp(browseOptions, label, BrowserDlg, dlg, MODAL, 0);
3054     SetWidgetLabel(&browseOptions[9], FileTypes[j]);
3055 }
3056
3057 static char *openName;
3058 FileProc fileProc;
3059 char *fileOpenMode;
3060 FILE *openFP;
3061
3062 void
3063 DelayedLoad ()
3064 {
3065   (void) (*fileProc)(openFP, 0, openName);
3066 }
3067
3068 void
3069 FileNamePopUp (char *label, char *def, char *filter, FileProc proc, char *openMode)
3070 {
3071     fileProc = proc;            /* I can't see a way not */
3072     fileOpenMode = openMode;    /*   to use globals here */
3073     FileNamePopUpWrapper(label, def, filter, proc, False, openMode, &openName, &openFP);
3074 }
3075
3076 void
3077 ActivateTheme (int col)
3078 {
3079     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap; else lineGap = defaultLineGap;
3080     InitDrawingParams(strcmp(oldPieceDir, appData.pieceDirectory));
3081     InitDrawingSizes(-1, 0);
3082     DrawPosition(True, NULL);
3083 }
3084