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