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