Kludge repair of expose after startup resize
[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     if(DialogExists(TagsDlg)) { // if already exists, alter title and content
1222         SetWidgetText(&tagsOptions[1], text, TagsDlg);
1223         SetDialogTitle(TagsDlg, title);
1224     }
1225     if(tagsText) free(tagsText); tagsText = strdup(text);
1226     tagsOptions[0].name = msg;
1227     MarkMenu("View.Tags", TagsDlg);
1228     GenericPopUp(tagsOptions, title, TagsDlg, BoardWindow, NONMODAL, appData.topLevel);
1229 }
1230
1231 void
1232 TagsPopUp (char *tags, char *msg)
1233 {
1234     NewTagsPopup(tags, cmailMsgLoaded ? msg : NULL);
1235 }
1236
1237 void
1238 EditTagsPopUp (char *tags, char **dest)
1239 {   // wrapper to preserve old name used in back-end
1240     resPtr = dest; 
1241     NewTagsPopup(tags, NULL);
1242 }
1243
1244 void
1245 TagsPopDown()
1246 {
1247     PopDown(TagsDlg);
1248     bookUp = False;
1249 }
1250
1251 void
1252 EditTagsProc ()
1253 {
1254   if (bookUp || !PopDown(TagsDlg)) EditTagsEvent();
1255 }
1256
1257 void
1258 AddBookMove (char *text)
1259 {
1260     AppendText(&tagsOptions[1], text);
1261 }
1262
1263 //---------------------------------------------- ICS Input Box ----------------------------------
1264
1265 char *icsText;
1266
1267 // [HGM] code borrowed from winboard.c (which should thus go to backend.c!)
1268 #define HISTORY_SIZE 64
1269 static char *history[HISTORY_SIZE];
1270 static int histIn = 0, histP = 0;
1271
1272 static void
1273 SaveInHistory (char *cmd)
1274 {
1275   if (history[histIn] != NULL) {
1276     free(history[histIn]);
1277     history[histIn] = NULL;
1278   }
1279   if (*cmd == NULLCHAR) return;
1280   history[histIn] = StrSave(cmd);
1281   histIn = (histIn + 1) % HISTORY_SIZE;
1282   if (history[histIn] != NULL) {
1283     free(history[histIn]);
1284     history[histIn] = NULL;
1285   }
1286   histP = histIn;
1287 }
1288
1289 static char *
1290 PrevInHistory (char *cmd)
1291 {
1292   int newhp;
1293   if (histP == histIn) {
1294     if (history[histIn] != NULL) free(history[histIn]);
1295     history[histIn] = StrSave(cmd);
1296   }
1297   newhp = (histP - 1 + HISTORY_SIZE) % HISTORY_SIZE;
1298   if (newhp == histIn || history[newhp] == NULL) return NULL;
1299   histP = newhp;
1300   return history[histP];
1301 }
1302
1303 static char *
1304 NextInHistory ()
1305 {
1306   if (histP == histIn) return NULL;
1307   histP = (histP + 1) % HISTORY_SIZE;
1308   return history[histP];
1309 }
1310 // end of borrowed code
1311
1312 #define INPUT 0
1313
1314 Option boxOptions[] = {
1315 {  30, T_TOP, 400, NULL, (void*) &icsText, "", NULL, TextBox, "" },
1316 {  0,  NO_OK,   0, NULL, NULL, "", NULL, EndMark , "" }
1317 };
1318
1319 void
1320 ICSInputSendText ()
1321 {
1322     char *val;
1323
1324     GetWidgetText(&boxOptions[INPUT], &val);
1325     SaveInHistory(val);
1326     SendMultiLineToICS(val);
1327     SetWidgetText(&boxOptions[INPUT], "", InputBoxDlg);
1328 }
1329
1330 void
1331 IcsKey (int n)
1332 {   // [HGM] input: let up-arrow recall previous line from history
1333     char *val = NULL; // to suppress spurious warning
1334
1335     if (!shellUp[InputBoxDlg]) return;
1336     switch(n) {
1337       case 0:
1338         ICSInputSendText();
1339         return;
1340       case 1:
1341         GetWidgetText(&boxOptions[INPUT], &val);
1342         val = PrevInHistory(val);
1343         break;
1344       case -1:
1345         val = NextInHistory();
1346     }
1347     SetWidgetText(&boxOptions[INPUT], val = val ? val : "", InputBoxDlg);
1348     SetInsertPos(&boxOptions[INPUT], strlen(val));
1349 }
1350
1351 void
1352 ICSInputBoxPopUp ()
1353 {
1354     MarkMenu("View.ICSInputBox", InputBoxDlg);
1355     if(GenericPopUp(boxOptions, _("ICS input box"), InputBoxDlg, BoardWindow, NONMODAL, 0))
1356         AddHandler(&boxOptions[INPUT], InputBoxDlg, 3);
1357     CursorAtEnd(&boxOptions[INPUT]);
1358 }
1359
1360 void
1361 IcsInputBoxProc ()
1362 {
1363     if (!PopDown(InputBoxDlg)) ICSInputBoxPopUp();
1364 }
1365
1366 //--------------------------------------------- Move Type In ------------------------------------------
1367
1368 static int TypeInOK P((int n));
1369
1370 Option typeOptions[] = {
1371 { 30, T_TOP, 400, NULL, (void*) &icsText, "", NULL, TextBox, "" },
1372 { 0,  NO_OK,   0, NULL, (void*) &TypeInOK, "", NULL, EndMark , "" }
1373 };
1374
1375 static int
1376 TypeInOK (int n)
1377 {
1378     TypeInDoneEvent(icsText);
1379     return TRUE;
1380 }
1381
1382 void
1383 PopUpMoveDialog (char firstchar)
1384 {
1385     static char buf[2];
1386     buf[0] = firstchar; ASSIGN(icsText, buf);
1387     if(GenericPopUp(typeOptions, _("Type a move"), TransientDlg, BoardWindow, MODAL, 0))
1388         AddHandler(&typeOptions[0], TransientDlg, 2);
1389     CursorAtEnd(&typeOptions[0]);
1390 }
1391
1392 void
1393 BoxAutoPopUp (char *buf)
1394 {       // only used in Xaw. GTK calls ConsoleAutoPopUp in stead (when we type to board)
1395         if(!appData.autoBox) return;
1396         if(appData.icsActive) { // text typed to board in ICS mode: divert to ICS input box
1397             if(DialogExists(InputBoxDlg)) { // box already exists: append to current contents
1398                 char *p, newText[MSG_SIZ];
1399                 GetWidgetText(&boxOptions[INPUT], &p);
1400                 snprintf(newText, MSG_SIZ, "%s%c", p, *buf);
1401                 SetWidgetText(&boxOptions[INPUT], newText, InputBoxDlg);
1402                 if(shellUp[InputBoxDlg]) HardSetFocus (&boxOptions[INPUT], InputBoxDlg); //why???
1403             } else icsText = buf; // box did not exist: make sure it pops up with char in it
1404             ICSInputBoxPopUp();
1405         } else PopUpMoveDialog(*buf);
1406 }
1407
1408 //------------------------------------------ Engine Settings ------------------------------------
1409
1410 void
1411 SettingsPopUp (ChessProgramState *cps)
1412 {
1413    if(!cps->nrOptions) { DisplayNote(_("Engine has no options")); return; }
1414    currentCps = cps;
1415    GenericPopUp(cps->option, _("Engine Settings"), TransientDlg, BoardWindow, MODAL, 0);
1416 }
1417
1418 void
1419 FirstSettingsProc ()
1420 {
1421     SettingsPopUp(&first);
1422 }
1423
1424 void
1425 SecondSettingsProc ()
1426 {
1427    if(WaitForEngine(&second, SettingsMenuIfReady)) return;
1428    SettingsPopUp(&second);
1429 }
1430
1431 //----------------------------------------------- Load Engine --------------------------------------
1432
1433 char *engineDir, *engineLine, *nickName, *params;
1434 Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick, secondEng;
1435
1436 static void EngSel P((int n, int sel));
1437 static int InstallOK P((int n));
1438
1439 static Option installOptions[] = {
1440 {   0,LR|T2T, 0, NULL, NULL, NULL, NULL, Label, N_("Select engine from list:") },
1441 { 300,LR|TB,200, NULL, (void*) engineMnemonic, (char*) &EngSel, NULL, ListBox, "" },
1442 { 0,SAME_ROW, 0, NULL, NULL, NULL, NULL, Break, NULL },
1443 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("or specify one below:") },
1444 {   0,  0,    0, NULL, (void*) &nickName, NULL, NULL, TextBox, N_("Nickname (optional):") },
1445 {   0,  0,    0, NULL, (void*) &useNick, NULL, NULL, CheckBox, N_("Use nickname in PGN player tags of engine-engine games") },
1446 {   0,  0,    0, NULL, (void*) &engineDir, NULL, NULL, PathName, N_("Engine Directory:") },
1447 {   0,  0,    0, NULL, (void*) &engineName, NULL, NULL, FileName, N_("Engine Command:") },
1448 {   0,  LR,   0, NULL, NULL, NULL, NULL, Label, N_("(Directory will be derived from engine path when empty)") },
1449 {   0,  0,    0, NULL, (void*) &isUCI, NULL, NULL, CheckBox, N_("UCI") },
1450 {   0,  0,    0, NULL, (void*) &v1, NULL, NULL, CheckBox, N_("WB protocol v1 (do not wait for engine features)") },
1451 {   0,  0,    0, NULL, (void*) &hasBook, NULL, NULL, CheckBox, N_("Must not use GUI book") },
1452 {   0,  0,    0, NULL, (void*) &addToList, NULL, NULL, CheckBox, N_("Add this engine to the list") },
1453 {   0,  0,    0, NULL, (void*) &storeVariant, NULL, NULL, CheckBox, N_("Force current variant with this engine") },
1454 {   0,  0,    0, NULL, (void*) &InstallOK, "", NULL, EndMark , "" }
1455 };
1456
1457 static int
1458 InstallOK (int n)
1459 {
1460     if(n && (n = SelectedListBoxItem(&installOptions[1])) > 0) { // called by pressing OK, and engine selected
1461         ASSIGN(engineLine, engineList[n]);
1462     }
1463     PopDown(TransientDlg); // early popdown, to allow FreezeUI to instate grab
1464     if(!secondEng) Load(&first, 0); else Load(&second, 1);
1465     return FALSE; // no double PopDown!
1466 }
1467
1468 static void
1469 EngSel (int n, int sel)
1470 {
1471     int nr;
1472     char buf[MSG_SIZ];
1473     if(sel < 1) buf[0] = NULLCHAR; // back to top level
1474     else if(engineList[sel][0] == '#') safeStrCpy(buf, engineList[sel], MSG_SIZ); // group header, open group
1475     else { // normal line, select engine
1476         ASSIGN(engineLine, engineList[sel]);
1477         InstallOK(0);
1478         return;
1479     }
1480     nr = NamesToList(firstChessProgramNames, engineList, engineMnemonic, buf); // replace list by only the group contents
1481     ASSIGN(engineMnemonic[0], buf);
1482     LoadListBox(&installOptions[1], _("# no engines are installed"), -1, -1);
1483     HighlightWithScroll(&installOptions[1], 0, nr);
1484 }
1485
1486 static void
1487 LoadEngineProc (int engineNr, char *title)
1488 {
1489    isUCI = storeVariant = v1 = useNick = False; addToList = hasBook = True; // defaults
1490    secondEng = engineNr;
1491    if(engineLine)   free(engineLine);   engineLine = strdup("");
1492    if(engineDir)    free(engineDir);    engineDir = strdup(".");
1493    if(nickName)     free(nickName);     nickName = strdup("");
1494    if(params)       free(params);       params = strdup("");
1495    ASSIGN(engineMnemonic[0], "");
1496    NamesToList(firstChessProgramNames, engineList, engineMnemonic, "");
1497    GenericPopUp(installOptions, title, TransientDlg, BoardWindow, MODAL, 0);
1498 }
1499
1500 void
1501 LoadEngine1Proc ()
1502 {
1503     LoadEngineProc (0, _("Load first engine"));
1504 }
1505
1506 void
1507 LoadEngine2Proc ()
1508 {
1509     LoadEngineProc (1, _("Load second engine"));
1510 }
1511
1512 //----------------------------------------------------- Edit Book -----------------------------------------
1513
1514 void
1515 EditBookProc ()
1516 {
1517     EditBookEvent();
1518 }
1519
1520 //--------------------------------------------------- New Shuffle Game ------------------------------
1521
1522 static void SetRandom P((int n));
1523
1524 static int
1525 ShuffleOK (int n)
1526 {
1527     ResetGameEvent();
1528     return 1;
1529 }
1530
1531 static Option shuffleOptions[] = {
1532   {   0,  0,    0, NULL, (void*) &shuffleOpenings, NULL, NULL, CheckBox, N_("shuffle") },
1533   {   0,  0,    0, NULL, (void*) &appData.fischerCastling, NULL, NULL, CheckBox, N_("Fischer castling") },
1534   { 0,-1,2000000000, NULL, (void*) &appData.defaultFrcPosition, "", NULL, Spin, N_("Start-position number:") },
1535   {   0,  0,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("randomize") },
1536   {   0,  SAME_ROW,    0, NULL, (void*) &SetRandom, NULL, NULL, Button, N_("pick fixed") },
1537   { 0,SAME_ROW, 0, NULL, (void*) &ShuffleOK, "", NULL, EndMark , "" }
1538 };
1539
1540 static void
1541 SetRandom (int n)
1542 {
1543     int r = n==2 ? -1 : random() & (1<<30)-1;
1544     char buf[MSG_SIZ];
1545     snprintf(buf, MSG_SIZ,  "%d", r);
1546     SetWidgetText(&shuffleOptions[1], buf, TransientDlg);
1547     SetWidgetState(&shuffleOptions[0], True);
1548 }
1549
1550 void
1551 ShuffleMenuProc ()
1552 {
1553     GenericPopUp(shuffleOptions, _("New Shuffle Game"), TransientDlg, BoardWindow, MODAL, 0);
1554 }
1555
1556 //------------------------------------------------------ Time Control -----------------------------------
1557
1558 static int TcOK P((int n));
1559 int tmpMoves, tmpTc, tmpInc, tmpOdds1, tmpOdds2, tcType;
1560
1561 static void SetTcType P((int n));
1562
1563 static char *
1564 Value (int n)
1565 {
1566         static char buf[MSG_SIZ];
1567         snprintf(buf, MSG_SIZ, "%d", n);
1568         return buf;
1569 }
1570
1571 static Option tcOptions[] = {
1572 {   0,  0,    0, NULL, (void*) &SetTcType, NULL, NULL, Button, N_("classical") },
1573 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("incremental") },
1574 {   0,SAME_ROW,0,NULL, (void*) &SetTcType, NULL, NULL, Button, N_("fixed max") },
1575 {   0,  0,  200, NULL, (void*) &tmpMoves, NULL, NULL, Spin, N_("Moves per session:") },
1576 {   0,  0,10000, NULL, (void*) &tmpTc,    NULL, NULL, Spin, N_("Initial time (min):") },
1577 {   0, 0, 10000, NULL, (void*) &tmpInc,   NULL, NULL, Spin, N_("Increment or max (sec/move):") },
1578 {   0,  0,    0, NULL, NULL, NULL, NULL, Label, N_("Time-Odds factors:") },
1579 {   0,  1, 1000, NULL, (void*) &tmpOdds1, NULL, NULL, Spin, N_("Engine #1") },
1580 {   0,  1, 1000, NULL, (void*) &tmpOdds2, NULL, NULL, Spin, N_("Engine #2 / Human") },
1581 {   0,  0,    0, NULL, (void*) &TcOK, "", NULL, EndMark , "" }
1582 };
1583
1584 static int
1585 TcOK (int n)
1586 {
1587     char *tc;
1588     if(tcType == 0 && tmpMoves <= 0) return 0;
1589     if(tcType == 2 && tmpInc <= 0) return 0;
1590     GetWidgetText(&tcOptions[4], &tc); // get original text, in case it is min:sec
1591     searchTime = 0;
1592     switch(tcType) {
1593       case 0:
1594         if(!ParseTimeControl(tc, -1, tmpMoves)) return 0;
1595         appData.movesPerSession = tmpMoves;
1596         ASSIGN(appData.timeControl, tc);
1597         appData.timeIncrement = -1;
1598         break;
1599       case 1:
1600         if(!ParseTimeControl(tc, tmpInc, 0)) return 0;
1601         ASSIGN(appData.timeControl, tc);
1602         appData.timeIncrement = tmpInc;
1603         break;
1604       case 2:
1605         searchTime = tmpInc;
1606     }
1607     appData.firstTimeOdds = first.timeOdds = tmpOdds1;
1608     appData.secondTimeOdds = second.timeOdds = tmpOdds2;
1609     Reset(True, True);
1610     return 1;
1611 }
1612
1613 static void
1614 SetTcType (int n)
1615 {
1616     switch(tcType = n) {
1617       case 0:
1618         SetWidgetText(&tcOptions[3], Value(tmpMoves), TransientDlg);
1619         SetWidgetText(&tcOptions[4], Value(tmpTc), TransientDlg);
1620         SetWidgetText(&tcOptions[5], _("Unused"), TransientDlg);
1621         break;
1622       case 1:
1623         SetWidgetText(&tcOptions[3], _("Unused"), TransientDlg);
1624         SetWidgetText(&tcOptions[4], Value(tmpTc), TransientDlg);
1625         SetWidgetText(&tcOptions[5], Value(tmpInc), TransientDlg);
1626         break;
1627       case 2:
1628         SetWidgetText(&tcOptions[3], _("Unused"), TransientDlg);
1629         SetWidgetText(&tcOptions[4], _("Unused"), TransientDlg);
1630         SetWidgetText(&tcOptions[5], Value(tmpInc), TransientDlg);
1631     }
1632 }
1633
1634 void
1635 TimeControlProc ()
1636 {
1637    tmpMoves = appData.movesPerSession;
1638    tmpInc = appData.timeIncrement; if(tmpInc < 0) tmpInc = 0;
1639    tmpOdds1 = tmpOdds2 = 1; tcType = 0;
1640    tmpTc = atoi(appData.timeControl);
1641    GenericPopUp(tcOptions, _("Time Control"), TransientDlg, BoardWindow, MODAL, 0);
1642    SetTcType(searchTime ? 2 : appData.timeIncrement < 0 ? 0 : 1);
1643 }
1644
1645 //------------------------------- Ask Question -----------------------------------------
1646
1647 int SendReply P((int n));
1648 char pendingReplyPrefix[MSG_SIZ];
1649 ProcRef pendingReplyPR;
1650 char *answer;
1651
1652 Option askOptions[] = {
1653 { 0, 0, 0, NULL, NULL, NULL, NULL, Label,  NULL },
1654 { 0, 0, 0, NULL, (void*) &answer, "", NULL, TextBox, "" },
1655 { 0, 0, 0, NULL, (void*) &SendReply, "", NULL, EndMark , "" }
1656 };
1657
1658 int
1659 SendReply (int n)
1660 {
1661     char buf[MSG_SIZ];
1662     int err;
1663     char *reply=answer;
1664 //    GetWidgetText(&askOptions[1], &reply);
1665     safeStrCpy(buf, pendingReplyPrefix, sizeof(buf)/sizeof(buf[0]) );
1666     if (*buf) strncat(buf, " ", MSG_SIZ - strlen(buf) - 1);
1667     strncat(buf, reply, MSG_SIZ - strlen(buf) - 1);
1668     strncat(buf, "\n",  MSG_SIZ - strlen(buf) - 1);
1669     OutputToProcess(pendingReplyPR, buf, strlen(buf), &err); // does not go into debug file??? => bug
1670     if (err) DisplayFatalError(_("Error writing to chess program"), err, 0);
1671     return TRUE;
1672 }
1673
1674 void
1675 AskQuestion (char *title, char *question, char *replyPrefix, ProcRef pr)
1676 {
1677     safeStrCpy(pendingReplyPrefix, replyPrefix, sizeof(pendingReplyPrefix)/sizeof(pendingReplyPrefix[0]) );
1678     pendingReplyPR = pr;
1679     ASSIGN(answer, "");
1680     askOptions[0].name = question;
1681     if(GenericPopUp(askOptions, title, AskDlg, BoardWindow, MODAL, 0))
1682         AddHandler(&askOptions[1], AskDlg, 2);
1683 }
1684
1685 //---------------------------- Promotion Popup --------------------------------------
1686
1687 static int count;
1688
1689 static void PromoPick P((int n));
1690
1691 static Option promoOptions[] = {
1692 {   0,         0,    0, NULL, (void*) &PromoPick, NULL, NULL, Button, NULL },
1693 {   0,  SAME_ROW,    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 | NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
1701 };
1702
1703 static void
1704 PromoPick (int n)
1705 {
1706     int promoChar = promoOptions[n+count].value;
1707
1708     PopDown(PromoDlg);
1709
1710     if (promoChar == 0) fromX = -1;
1711     if (fromX == -1) return;
1712
1713     if (! promoChar) {
1714         fromX = fromY = -1;
1715         ClearHighlights();
1716         return;
1717     }
1718     if(promoChar == '=' && !IS_SHOGI(gameInfo.variant)) promoChar = NULLCHAR;
1719     UserMoveEvent(fromX, fromY, toX, toY, promoChar);
1720
1721     if (!appData.highlightLastMove || gotPremove) ClearHighlights();
1722     if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
1723     fromX = fromY = -1;
1724 }
1725
1726 static void
1727 SetPromo (char *name, int nr, char promoChar)
1728 {
1729     ASSIGN(promoOptions[nr].name, name);
1730     promoOptions[nr].value = promoChar;
1731     promoOptions[nr].min = SAME_ROW;
1732 }
1733
1734 void
1735 PromotionPopUp (char choice)
1736 { // choice depends on variant: prepare dialog acordingly
1737   count = 8;
1738   SetPromo(_("Cancel"), --count, -1); // Beware: GenericPopUp cannot handle user buttons named "cancel" (lowe case)!
1739   if(choice != '+') {
1740     if (!appData.testLegality || gameInfo.variant == VariantSuicide ||
1741         gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove) ||
1742         gameInfo.variant == VariantGiveaway) {
1743       SetPromo(_("King"), --count, 'k');
1744     }
1745     if(gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove)) {
1746       SetPromo(_("Captain"), --count, 'c');
1747       SetPromo(_("Lieutenant"), --count, 'l');
1748       SetPromo(_("General"), --count, 'g');
1749       SetPromo(_("Warlord"), --count, 'w');
1750     } else {
1751       SetPromo(_("Knight"), --count, 'n');
1752       SetPromo(_("Bishop"), --count, 'b');
1753       SetPromo(_("Rook"), --count, 'r');
1754       if(gameInfo.variant == VariantCapablanca ||
1755          gameInfo.variant == VariantGothic ||
1756          gameInfo.variant == VariantCapaRandom) {
1757         SetPromo(_("Archbishop"), --count, 'a');
1758         SetPromo(_("Chancellor"), --count, 'c');
1759       }
1760       SetPromo(_("Queen"), --count, 'q');
1761       if(gameInfo.variant == VariantChuChess)
1762         SetPromo(_("Lion"), --count, 'l');
1763     }
1764   } else // [HGM] shogi
1765   {
1766       SetPromo(_("Defer"), --count, '=');
1767       SetPromo(_("Promote"), --count, '+');
1768   }
1769   promoOptions[count].min = 0;
1770   GenericPopUp(promoOptions + count, "Promotion", PromoDlg, BoardWindow, NONMODAL, 0);
1771 }
1772
1773 //---------------------------- Chat Windows ----------------------------------------------
1774
1775 static char *line, *memo, *chatMemo, *partner, *texts[MAX_CHAT], dirty[MAX_CHAT], *inputs[MAX_CHAT], *icsLine, *tmpLine;
1776 static int activePartner;
1777 int hidden = 1;
1778
1779 void ChatSwitch P((int n));
1780 int  ChatOK P((int n));
1781
1782 #define CHAT_ICS     6
1783 #define CHAT_PARTNER 8
1784 #define CHAT_OUT    11
1785 #define CHAT_PANE   12
1786 #define CHAT_IN     13
1787
1788 void PaneSwitch P((void));
1789 void ClearChat P((void));
1790
1791 WindowPlacement wpTextMenu;
1792
1793 int
1794 ContextMenu (Option *opt, int button, int x, int y, char *text, int index)
1795 { // callback for ICS-output clicks; handles button 3, passes on other events
1796   int h;
1797   if(button == -3) return TRUE; // supress default GTK context menu on up-click
1798   if(button != 3) return FALSE;
1799   if(index == -1) { // pre-existing selection in memo
1800     strncpy(clickedWord, text, MSG_SIZ);
1801   } else { // figure out what word was clicked
1802     char *start, *end;
1803     start = end = text + index;
1804     while(isalnum(*end)) end++;
1805     while(start > text && isalnum(start[-1])) start--;
1806     clickedWord[0] = NULLCHAR;
1807     if(end-start >= 80) end = start + 80; // intended for small words and numbers
1808     strncpy(clickedWord, start, end-start); clickedWord[end-start] = NULLCHAR;
1809   }
1810   click = !shellUp[TextMenuDlg]; // request auto-popdown of textmenu when we popped it up
1811   h = wpTextMenu.height; // remembered height of text menu
1812   if(h <= 0) h = 65;     // when not available, position w.r.t. top
1813   GetPlacement(ChatDlg, &wpTextMenu);
1814   if(opt->target == (void*) &chatMemo) wpTextMenu.y += (wpTextMenu.height - 30)/2; // click in chat
1815   wpTextMenu.x += x - 50; wpTextMenu.y += y - h + 50; // request positioning
1816   if(wpTextMenu.x < 0) wpTextMenu.x = 0;
1817   if(wpTextMenu.y < 0) wpTextMenu.y = 0;
1818   wpTextMenu.width = wpTextMenu.height = -1;
1819   IcsTextPopUp();
1820   return TRUE;
1821 }
1822
1823 Option chatOptions[] = {
1824 {  0,  0,   0, NULL, NULL, "", NULL, Label , N_("Chats:") },
1825 { 1, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
1826 { 2, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
1827 { 3, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
1828 { 4, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
1829 { 5, SAME_ROW|TT, 75, NULL, (void*) &ChatSwitch, NULL, NULL, Button, N_("New Chat") },
1830 { 250, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &memo, NULL, (void*) &ContextMenu, TextBox, "" },
1831 {  0,  0,   0, NULL, NULL, "", NULL, Break , "" },
1832 { 0,   T_TOP,    100, NULL, (void*) &partner, NULL, NULL, TextBox, N_("Chat partner:") },
1833 {  0, SAME_ROW, 0, NULL, (void*) &ClearChat,  NULL, NULL, Button, N_("End Chat") },
1834 {  0, SAME_ROW, 0, NULL, (void*) &PaneSwitch, NULL, NULL, Button, N_("Hide") },
1835 { 250, T_VSCRL | T_FILL | T_WRAP | T_TOP,    510, NULL, (void*) &chatMemo, NULL, (void*) &ContextMenu, TextBox, "" },
1836 {  0,  0,   0, NULL, NULL, "", NULL, Break , "" },
1837 {  0,    0,  510, NULL, (void*) &line, NULL, NULL, TextBox, "" },
1838 { 0, NO_OK|SAME_ROW, 0, NULL, (void*) &ChatOK, NULL, NULL, EndMark , "" }
1839 };
1840
1841 static void
1842 PutText (char *text, int pos)
1843 {
1844     char buf[MSG_SIZ], *p;
1845     DialogClass dlg = ChatDlg;
1846     Option *opt = &chatOptions[CHAT_IN];
1847
1848     if(strstr(text, "$add ") == text) {
1849         GetWidgetText(&boxOptions[INPUT], &p);
1850         snprintf(buf, MSG_SIZ, "%s%s", p, text+5); text = buf;
1851         pos += strlen(p) - 5;
1852     }
1853     if(shellUp[InputBoxDlg]) opt = &boxOptions[INPUT], dlg = InputBoxDlg; // for the benefit of Xaw give priority to ICS Input Box
1854     SetWidgetText(opt, text, dlg);
1855     SetInsertPos(opt, pos);
1856     HardSetFocus(opt, dlg);
1857     CursorAtEnd(opt);
1858 }
1859
1860 int
1861 IcsHist (int n, Option *opt, DialogClass dlg)
1862 {   // [HGM] input: let up-arrow recall previous line from history
1863     char *val = NULL; // to suppress spurious warning
1864     int chat, start;
1865
1866     if(opt != &chatOptions[CHAT_IN] && !(opt == &chatOptions[CHAT_PARTNER] && n == 33)) return 0;
1867     switch(n) {
1868       case 33: // <Esc>
1869         if(hidden) BoardToTop();
1870         else PaneSwitch();
1871         break;
1872       case 15:
1873         NewChat(lastTalker);
1874         break;
1875       case 14:
1876         for(chat=0; chat < MAX_CHAT; chat++) if(!chatPartner[chat][0]) break;
1877         if(chat < MAX_CHAT) ChatSwitch(chat + 1);
1878         break;
1879       case 10: // <Tab>
1880         chat = start = (activePartner - hidden + MAX_CHAT) % MAX_CHAT;
1881         while(!dirty[chat = (chat + 1)%MAX_CHAT]) if(chat == start) break;
1882         if(!dirty[chat])
1883         while(!chatPartner[chat = (chat + 1)%MAX_CHAT][0]) if(chat == start) break;
1884         if(chat == start && hidden) chat = 0; // if all unused, start left
1885         ChatSwitch(chat + 1);
1886         break;
1887       case 1:
1888         GetWidgetText(opt, &val);
1889         val = PrevInHistory(val);
1890         break;
1891       case -1:
1892         val = NextInHistory();
1893     }
1894     SetWidgetText(opt, val = val ? val : "", dlg);
1895     SetInsertPos(opt, strlen(val));
1896     return 1;
1897 }
1898
1899 void
1900 OutputChatMessage (int partner, char *mess)
1901 {
1902     char *p = texts[partner];
1903     int len = strlen(mess) + 1;
1904
1905     if(p) len += strlen(p);
1906     texts[partner] = (char*) malloc(len);
1907     snprintf(texts[partner], len, "%s%s", p ? p : "", mess);
1908     FREE(p);
1909     if(partner == activePartner && !hidden) {
1910         AppendText(&chatOptions[CHAT_OUT], mess);
1911         SetInsertPos(&chatOptions[CHAT_OUT], len-2);
1912     } else {
1913         SetColor("#FFC000", &chatOptions[partner + 1]);
1914         dirty[partner] = 1;
1915     }
1916 }
1917
1918 int
1919 ChatOK (int n)
1920 {   // can only be called through <Enter> in chat-partner text-edit, as there is no OK button
1921     char buf[MSG_SIZ];
1922
1923     if(!hidden && (!partner || strcmp(partner, chatPartner[activePartner]))) {
1924         safeStrCpy(chatPartner[activePartner], partner, MSG_SIZ);
1925         SetWidgetText(&chatOptions[CHAT_OUT], "", -1); // clear text if we alter partner
1926         SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg); // clear text if we alter partner
1927         SetWidgetLabel(&chatOptions[activePartner+1], chatPartner[activePartner][0] ? chatPartner[activePartner] : _("New Chat"));
1928         HardSetFocus(&chatOptions[CHAT_IN], 0);
1929     }
1930     if(line[0] || hidden) { // something was typed (for ICS commands we also allow empty line!)
1931         SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg);
1932         // from here on it could be back-end
1933         if(line[strlen(line)-1] == '\n') line[strlen(line)-1] = NULLCHAR;
1934         SaveInHistory(line);
1935         if(hidden || !*chatPartner[activePartner]) snprintf(buf, MSG_SIZ, "%s\n", line); else // command for ICS
1936         if(!strcmp("whispers", chatPartner[activePartner]))
1937               snprintf(buf, MSG_SIZ, "whisper %s\n", line); // WHISPER box uses "whisper" to send
1938         else if(!strcmp("shouts", chatPartner[activePartner]))
1939               snprintf(buf, MSG_SIZ, "shout %s\n", line); // SHOUT box uses "shout" to send
1940         else {
1941             if(!atoi(chatPartner[activePartner])) {
1942                 snprintf(buf, MSG_SIZ, "> %s\n", line); // echo only tells to handle, not channel
1943                 OutputChatMessage(activePartner, buf);
1944                 snprintf(buf, MSG_SIZ, "xtell %s %s\n", chatPartner[activePartner], line);
1945             } else
1946                 snprintf(buf, MSG_SIZ, "tell %s %s\n", chatPartner[activePartner], line);
1947         }
1948         SendToICS(buf);
1949     }
1950     return FALSE; // never pop down
1951 }
1952
1953 void
1954 DelayedSetText ()
1955 {
1956     SetWidgetText(&chatOptions[CHAT_IN], tmpLine, ChatDlg);
1957     SetInsertPos(&chatOptions[CHAT_IN], strlen(tmpLine));
1958 }
1959
1960 void
1961 DelayedScroll ()
1962 {   // If we do this immediately it does it before shrinking the memo, so the lower half remains hidden (Ughh!)
1963     SetInsertPos(&chatOptions[CHAT_ICS], 999999);
1964     SetWidgetText(&chatOptions[CHAT_IN], tmpLine, ChatDlg);
1965     SetInsertPos(&chatOptions[CHAT_IN], strlen(tmpLine));
1966 }
1967
1968 void
1969 ChatSwitch (int n)
1970 {
1971     int i, j;
1972     char *v;
1973     Show(&chatOptions[CHAT_PANE], 0); // show
1974     if(hidden) ScheduleDelayedEvent(DelayedScroll, 50); // Awful!
1975     else ScheduleDelayedEvent(DelayedSetText, 50);
1976     GetWidgetText(&chatOptions[CHAT_IN], &v);
1977     if(hidden) { ASSIGN(icsLine, v); } else { ASSIGN(inputs[activePartner], v); }
1978     hidden = 0;
1979     activePartner = --n;
1980     if(!texts[n]) texts[n] = strdup("");
1981     dirty[n] = 0;
1982     SetWidgetText(&chatOptions[CHAT_OUT], texts[n], ChatDlg);
1983     SetInsertPos(&chatOptions[CHAT_OUT], strlen(texts[n]));
1984     SetWidgetText(&chatOptions[CHAT_PARTNER], chatPartner[n], ChatDlg);
1985     for(i=j=0; i<MAX_CHAT; i++) {
1986         SetWidgetLabel(&chatOptions[++j], *chatPartner[i] ? chatPartner[i] : _("New Chat"));
1987         SetColor(dirty[i] ? "#FFC000" : "#FFFFFF", &chatOptions[j]);
1988     }
1989     if(!inputs[n]) { ASSIGN(inputs[n], ""); }
1990 //    SetWidgetText(&chatOptions[CHAT_IN], inputs[n], ChatDlg); // does not work (in this widget only)
1991 //    SetInsertPos(&chatOptions[CHAT_IN], strlen(inputs[n]));
1992     tmpLine = inputs[n]; // for the delayed event
1993     HardSetFocus(&chatOptions[strcmp(chatPartner[n], "") ? CHAT_IN : CHAT_PARTNER], 0);
1994 }
1995
1996 void
1997 PaneSwitch ()
1998 {
1999     char *v;
2000     Show(&chatOptions[CHAT_PANE], hidden = 1); // hide
2001     GetWidgetText(&chatOptions[CHAT_IN], &v);
2002     ASSIGN(inputs[activePartner], v);
2003     if(!icsLine) { ASSIGN(icsLine, ""); }
2004     tmpLine = icsLine; ScheduleDelayedEvent(DelayedSetText, 50);
2005 //    SetWidgetText(&chatOptions[CHAT_IN], icsLine, ChatDlg); // does not work (in this widget only)
2006 //    SetInsertPos(&chatOptions[CHAT_IN], strlen(icsLine));
2007 }
2008
2009 void
2010 ClearChat ()
2011 {   // clear the chat to make it free for other use
2012     chatPartner[activePartner][0] = NULLCHAR;
2013     ASSIGN(texts[activePartner], "");
2014     ASSIGN(inputs[activePartner], "");
2015     SetWidgetText(&chatOptions[CHAT_PARTNER], "", ChatDlg);
2016     SetWidgetText(&chatOptions[CHAT_OUT], "", ChatDlg);
2017     SetWidgetText(&chatOptions[CHAT_IN], "", ChatDlg);
2018     SetWidgetLabel(&chatOptions[activePartner+1], _("New Chat"));
2019     HardSetFocus(&chatOptions[CHAT_PARTNER], 0);
2020 }
2021
2022 static void
2023 NewChat (char *name)
2024 {   // open a chat on program request. If no empty one available, use last
2025     int i;
2026     for(i=0; i<MAX_CHAT-1; i++) if(!chatPartner[i][0]) break;
2027     safeStrCpy(chatPartner[i], name, MSG_SIZ);
2028     ChatSwitch(i+1);
2029 }
2030
2031 void
2032 ConsoleWrite(char *message, int count)
2033 {
2034     if(shellUp[ChatDlg]) {
2035         AppendColorized(&chatOptions[CHAT_ICS], message, count);
2036         SetInsertPos(&chatOptions[CHAT_ICS], 999999);
2037     }
2038 }
2039
2040 void
2041 ChatPopUp ()
2042 {
2043     if(GenericPopUp(chatOptions, _("ICS Interaction"), ChatDlg, BoardWindow, NONMODAL, appData.topLevel))
2044         AddHandler(&chatOptions[CHAT_PARTNER], ChatDlg, 2), AddHandler(&chatOptions[CHAT_IN], ChatDlg, 2); // treats return as OK
2045     Show(&chatOptions[CHAT_PANE], hidden = 1); // hide
2046 //    HardSetFocus(&chatOptions[CHAT_IN], 0);
2047     MarkMenu("View.OpenChatWindow", ChatDlg);
2048     CursorAtEnd(&chatOptions[CHAT_IN]);
2049 }
2050
2051 void
2052 ChatProc ()
2053 {
2054     if(shellUp[ChatDlg]) PopDown(ChatDlg);
2055     else ChatPopUp();
2056 }
2057
2058 void
2059 ConsoleAutoPopUp (char *buf)
2060 {
2061         if(!appData.autoBox) return;
2062         if(appData.icsActive) { // text typed to board in ICS mode: divert to ICS input box
2063             if(DialogExists(ChatDlg)) { // box already exists: append to current contents
2064                 char *p, newText[MSG_SIZ];
2065                 GetWidgetText(&chatOptions[CHAT_IN], &p);
2066                 snprintf(newText, MSG_SIZ, "%s%c", p, *buf);
2067                 SetWidgetText(&chatOptions[CHAT_IN], newText, ChatDlg);
2068                 if(shellUp[ChatDlg]) HardSetFocus (&chatOptions[CHAT_IN], ChatDlg); //why???
2069             } else { ASSIGN(line, buf); } // box did not exist: make sure it pops up with char in it
2070             ChatPopUp();
2071         } else PopUpMoveDialog(*buf);
2072 }
2073
2074 //--------------------------------- Game-List options dialog ------------------------------------------
2075
2076 char *strings[LPUSERGLT_SIZE];
2077 int stringPtr;
2078
2079 void
2080 GLT_ClearList ()
2081 {
2082     strings[0] = NULL;
2083     stringPtr = 0;
2084 }
2085
2086 void
2087 GLT_AddToList (char *name)
2088 {
2089     strings[stringPtr++] = name;
2090     strings[stringPtr] = NULL;
2091 }
2092
2093 Boolean
2094 GLT_GetFromList (int index, char *name)
2095 {
2096   safeStrCpy(name, strings[index], MSG_SIZ);
2097   return TRUE;
2098 }
2099
2100 void
2101 GLT_DeSelectList ()
2102 {
2103 }
2104
2105 static void GLT_Button P((int n));
2106 static int GLT_OK P((int n));
2107
2108 static Option listOptions[] = {
2109 {300, LR|TB, 200, NULL, (void*) strings, "", NULL, ListBox, "" }, // For GTK we need to specify a height, as default would just show 3 lines
2110 { 0,    0,     0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("factory") },
2111 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("up") },
2112 { 0, SAME_ROW, 0, NULL, (void*) &GLT_Button, NULL, NULL, Button, N_("down") },
2113 { 0, SAME_ROW, 0, NULL, (void*) &GLT_OK, "", NULL, EndMark , "" }
2114 };
2115
2116 static int
2117 GLT_OK (int n)
2118 {
2119     GLT_ParseList();
2120     appData.gameListTags = strdup(lpUserGLT);
2121     return 1;
2122 }
2123
2124 static void
2125 GLT_Button (int n)
2126 {
2127     int index = SelectedListBoxItem (&listOptions[0]);
2128     char *p;
2129     if (index < 0) {
2130         DisplayError(_("No tag selected"), 0);
2131         return;
2132     }
2133     p = strings[index];
2134     if (n == 3) {
2135         if(index >= strlen(GLT_ALL_TAGS)) return;
2136         strings[index] = strings[index+1];
2137         strings[++index] = p;
2138         LoadListBox(&listOptions[0], "?", index, index-1); // only change the two specified entries
2139     } else
2140     if (n == 2) {
2141         if(index == 0) return;
2142         strings[index] = strings[index-1];
2143         strings[--index] = p;
2144         LoadListBox(&listOptions[0], "?", index, index+1);
2145     } else
2146     if (n == 1) {
2147       safeStrCpy(lpUserGLT, GLT_DEFAULT_TAGS, LPUSERGLT_SIZE);
2148       GLT_TagsToList(lpUserGLT);
2149       index = 0;
2150       LoadListBox(&listOptions[0], "?", -1, -1);
2151     }
2152     HighlightListBoxItem(&listOptions[0], index);
2153 }
2154
2155 void
2156 GameListOptionsPopUp (DialogClass parent)
2157 {
2158     safeStrCpy(lpUserGLT, appData.gameListTags, LPUSERGLT_SIZE);
2159     GLT_TagsToList(lpUserGLT);
2160
2161     GenericPopUp(listOptions, _("Game-list options"), TransientDlg, parent, MODAL, 0);
2162 }
2163
2164 void
2165 GameListOptionsProc ()
2166 {
2167     GameListOptionsPopUp(BoardWindow);
2168 }
2169
2170 //----------------------------- Error popup in various uses -----------------------------
2171
2172 /*
2173  * [HGM] Note:
2174  * XBoard has always had some pathologic behavior with multiple simultaneous error popups,
2175  * (which can occur even for modal popups when asynchrounous events, e.g. caused by engine, request a popup),
2176  * and this new implementation reproduces that as well:
2177  * Only the shell of the last instance is remembered in shells[ErrorDlg] (which replaces errorShell),
2178  * so that PopDowns ordered from the code always refer to that instance, and once that is down,
2179  * have no clue as to how to reach the others. For the Delete Window button calling PopDown this
2180  * has now been repaired, as the action routine assigned to it gets the shell passed as argument.
2181  */
2182
2183 int errorUp = False;
2184
2185 void
2186 ErrorPopDown ()
2187 {
2188     if (!errorUp) return;
2189     dialogError = errorUp = False;
2190     PopDown(ErrorDlg); PopDown(FatalDlg); // on explicit request we pop down any error dialog
2191     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
2192 }
2193
2194 static int
2195 ErrorOK (int n)
2196 {
2197     dialogError = errorUp = False;
2198     PopDown(n == 1 ? FatalDlg : ErrorDlg); // kludge: non-modal dialogs have one less (dummy) option
2199     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
2200     return FALSE; // prevent second Popdown !
2201 }
2202
2203 static Option errorOptions[] = {
2204 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // dummy option: will never be displayed
2205 {   0,  0,    0, NULL, NULL, NULL, NULL, Label,  NULL }, // textValue field will be set before popup
2206 { 0,NO_CANCEL,0, NULL, (void*) &ErrorOK, "", NULL, EndMark , "" }
2207 };
2208
2209 void
2210 ErrorPopUp (char *title, char *label, int modal)
2211 {
2212     errorUp = True;
2213     errorOptions[1].name = label;
2214     if(dialogError = shellUp[TransientDlg])
2215         GenericPopUp(errorOptions+1, title, FatalDlg, TransientDlg, MODAL, 0); // pop up as daughter of the transient dialog
2216     else
2217         GenericPopUp(errorOptions+modal, title, modal ? FatalDlg: ErrorDlg, BoardWindow, modal, 0); // kludge: option start address indicates modality
2218 }
2219
2220 void
2221 DisplayError (String message, int error)
2222 {
2223     char buf[MSG_SIZ];
2224
2225     if (error == 0) {
2226         if (appData.debugMode || appData.matchMode) {
2227             fprintf(stderr, "%s: %s\n", programName, message);
2228         }
2229     } else {
2230         if (appData.debugMode || appData.matchMode) {
2231             fprintf(stderr, "%s: %s: %s\n",
2232                     programName, message, strerror(error));
2233         }
2234         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
2235         message = buf;
2236     }
2237     ErrorPopUp(_("Error"), message, FALSE);
2238 }
2239
2240
2241 void
2242 DisplayMoveError (String message)
2243 {
2244     fromX = fromY = -1;
2245     ClearHighlights();
2246     DrawPosition(TRUE, NULL); // selective redraw would miss the from-square of the rejected move, displayed empty after drag, but not marked damaged!
2247     if (appData.debugMode || appData.matchMode) {
2248         fprintf(stderr, "%s: %s\n", programName, message);
2249     }
2250     if (appData.popupMoveErrors) {
2251         ErrorPopUp(_("Error"), message, FALSE);
2252     } else {
2253         DisplayMessage(message, "");
2254     }
2255 }
2256
2257
2258 void
2259 DisplayFatalError (String message, int error, int status)
2260 {
2261     char buf[MSG_SIZ];
2262
2263     errorExitStatus = status;
2264     if (error == 0) {
2265         fprintf(stderr, "%s: %s\n", programName, message);
2266     } else {
2267         fprintf(stderr, "%s: %s: %s\n",
2268                 programName, message, strerror(error));
2269         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
2270         message = buf;
2271     }
2272     if(mainOptions[W_BOARD].handle) {
2273         if (appData.popupExitMessage) {
2274             ErrorPopUp(status ? _("Fatal Error") : _("Exiting"), message, TRUE);
2275         } else {
2276             ExitEvent(status);
2277         }
2278     }
2279 }
2280
2281 void
2282 DisplayInformation (String message)
2283 {
2284     ErrorPopDown();
2285     ErrorPopUp(_("Information"), message, TRUE);
2286 }
2287
2288 void
2289 DisplayNote (String message)
2290 {
2291     ErrorPopDown();
2292     ErrorPopUp(_("Note"), message, FALSE);
2293 }
2294
2295 void
2296 DisplayTitle (char *text)
2297 {
2298     char title[MSG_SIZ];
2299     char icon[MSG_SIZ];
2300
2301     if (text == NULL) text = "";
2302
2303     if(partnerUp) { SetDialogTitle(DummyDlg, text); return; }
2304
2305     if (*text != NULLCHAR) {
2306       safeStrCpy(icon, text, sizeof(icon)/sizeof(icon[0]) );
2307       safeStrCpy(title, text, sizeof(title)/sizeof(title[0]) );
2308     } else if (appData.icsActive) {
2309         snprintf(icon, sizeof(icon), "%s", appData.icsHost);
2310         snprintf(title, sizeof(title), "%s: %s", programName, appData.icsHost);
2311     } else if (appData.cmailGameName[0] != NULLCHAR) {
2312         snprintf(icon, sizeof(icon), "%s", "CMail");
2313         snprintf(title,sizeof(title), "%s: %s", programName, "CMail");
2314 #ifdef GOTHIC
2315     // [HGM] license: This stuff should really be done in back-end, but WinBoard already had a pop-up for it
2316     } else if (gameInfo.variant == VariantGothic) {
2317       safeStrCpy(icon,  programName, sizeof(icon)/sizeof(icon[0]) );
2318       safeStrCpy(title, GOTHIC,     sizeof(title)/sizeof(title[0]) );
2319 #endif
2320 #ifdef FALCON
2321     } else if (gameInfo.variant == VariantFalcon) {
2322       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
2323       safeStrCpy(title, FALCON, sizeof(title)/sizeof(title[0]) );
2324 #endif
2325     } else if (appData.noChessProgram) {
2326       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
2327       safeStrCpy(title, programName, sizeof(title)/sizeof(title[0]) );
2328     } else {
2329       safeStrCpy(icon, first.tidy, sizeof(icon)/sizeof(icon[0]) );
2330         snprintf(title,sizeof(title), "%s: %s", programName, first.tidy);
2331     }
2332     SetWindowTitle(text, title, icon);
2333 }
2334
2335 #define PAUSE_BUTTON "P"
2336 #define PIECE_MENU_SIZE 18
2337 static String pieceMenuStrings[2][PIECE_MENU_SIZE+1] = {
2338     { N_("White"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
2339       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
2340       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
2341       N_("Empty square"), N_("Clear board"), NULL },
2342     { N_("Black"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
2343       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
2344       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
2345       N_("Empty square"), N_("Clear board"), NULL }
2346 };
2347 /* must be in same order as pieceMenuStrings! */
2348 static ChessSquare pieceMenuTranslation[2][PIECE_MENU_SIZE] = {
2349     { WhitePlay, (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2350         WhiteRook, WhiteQueen, WhiteKing, (ChessSquare) 0, WhiteAlfil,
2351         WhiteCannon, WhiteAngel, WhiteMarshall, (ChessSquare) 0,
2352         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2353     { BlackPlay, (ChessSquare) 0, BlackPawn, BlackKnight, BlackBishop,
2354         BlackRook, BlackQueen, BlackKing, (ChessSquare) 0, BlackAlfil,
2355         BlackCannon, BlackAngel, BlackMarshall, (ChessSquare) 0,
2356         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
2357 };
2358
2359 #define DROP_MENU_SIZE 6
2360 static String dropMenuStrings[DROP_MENU_SIZE+1] = {
2361     "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"), N_("Queen"), NULL
2362   };
2363 /* must be in same order as dropMenuStrings! */
2364 static ChessSquare dropMenuTranslation[DROP_MENU_SIZE] = {
2365     (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
2366     WhiteRook, WhiteQueen
2367 };
2368
2369 // [HGM] experimental code to pop up window just like the main window, using GenercicPopUp
2370
2371 static Option *Exp P((int n, int x, int y));
2372 void MenuCallback P((int n));
2373 void SizeKludge P((int n));
2374 static Option *LogoW P((int n, int x, int y));
2375 static Option *LogoB P((int n, int x, int y));
2376
2377 static int pmFromX = -1, pmFromY = -1;
2378 void *userLogo;
2379
2380 void
2381 DisplayLogos (Option *w1, Option *w2)
2382 {
2383         void *whiteLogo = first.programLogo, *blackLogo = second.programLogo;
2384         if(appData.autoLogo) {
2385
2386           switch(gameMode) { // pick logos based on game mode
2387             case IcsObserving:
2388                 whiteLogo = second.programLogo; // ICS logo
2389                 blackLogo = second.programLogo;
2390             default:
2391                 break;
2392             case IcsPlayingWhite:
2393                 if(!appData.zippyPlay) whiteLogo = userLogo;
2394                 blackLogo = second.programLogo; // ICS logo
2395                 break;
2396             case IcsPlayingBlack:
2397                 whiteLogo = second.programLogo; // ICS logo
2398                 blackLogo = appData.zippyPlay ? first.programLogo : userLogo;
2399                 break;
2400             case TwoMachinesPlay:
2401                 if(first.twoMachinesColor[0] == 'b') {
2402                     whiteLogo = second.programLogo;
2403                     blackLogo = first.programLogo;
2404                 }
2405                 break;
2406             case MachinePlaysWhite:
2407                 blackLogo = userLogo;
2408                 break;
2409             case MachinePlaysBlack:
2410                 whiteLogo = userLogo;
2411                 blackLogo = first.programLogo;
2412           }
2413         }
2414         DrawLogo(w1, whiteLogo);
2415         DrawLogo(w2, blackLogo);
2416 }
2417
2418 static void
2419 PMSelect (int n)
2420 {   // user callback for board context menus
2421     if (pmFromX < 0 || pmFromY < 0) return;
2422     if(n == W_DROP) DropMenuEvent(dropMenuTranslation[values[n]], pmFromX, pmFromY);
2423     else EditPositionMenuEvent(pieceMenuTranslation[n - W_MENUW][values[n]], pmFromX, pmFromY);
2424 }
2425
2426 static void
2427 CCB (int n)
2428 {
2429     shiftKey = (ShiftKeys() & 3) != 0;
2430     if(n < 0) { // button != 1
2431         n = -n;
2432         if(shiftKey && (gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack)) {
2433             AdjustClock(n == W_BLACK, 1);
2434         }
2435     } else
2436     ClockClick(n == W_BLACK);
2437 }
2438
2439 Option mainOptions[] = { // description of main window in terms of generic dialog creator
2440 { 0, 0xCA, 0, NULL, NULL, "", NULL, BarBegin, "" }, // menu bar
2441   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("File") },
2442   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Edit") },
2443   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("View") },
2444   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Mode") },
2445   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Action") },
2446   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Engine") },
2447   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Options") },
2448   { 0, COMBO_CALLBACK, 0, NULL, (void*)&MenuCallback, NULL, NULL, DropDown, N_("Help") },
2449 { 0, 0, 0, NULL, (void*)&SizeKludge, "", NULL, BarEnd, "" },
2450 { 0, LR|T2T|BORDER|SAME_ROW, 0, NULL, NULL, "", NULL, Label, "1" }, // optional title in window
2451 { 50,    LL|TT,            100, NULL, (void*) &LogoW, NULL, NULL, Skip, "" }, // white logo
2452 { 12,   L2L|T2T,           200, NULL, (void*) &CCB, NULL, NULL, Label, "White" }, // white clock
2453 { 13,   R2R|T2T|SAME_ROW,  200, NULL, (void*) &CCB, NULL, NULL, Label, "Black" }, // black clock
2454 { 50,    RR|TT|SAME_ROW,   100, NULL, (void*) &LogoB, NULL, NULL, Skip, "" }, // black logo
2455 { 0, LR|T2T|BORDER,        401, NULL, NULL, "", NULL, Skip, "2" }, // backup for title in window (if no room for other)
2456 { 0, LR|T2T|BORDER,        270, NULL, NULL, "", NULL, Label, "message" }, // message field
2457 { 0, RR|TT|SAME_ROW,       125, NULL, NULL, "", NULL, BoxBegin, "" }, // (optional) button bar
2458   { 0,    0,     0, NULL, (void*) &ToStartEvent, NULL, NULL, Button, N_("<<") },
2459   { 0, SAME_ROW, 0, NULL, (void*) &BackwardEvent, NULL, NULL, Button, N_("<") },
2460   { 0, SAME_ROW, 0, NULL, (void*) &PauseEvent, NULL, NULL, Button, N_(PAUSE_BUTTON) },
2461   { 0, SAME_ROW, 0, NULL, (void*) &ForwardEvent, NULL, NULL, Button, N_(">") },
2462   { 0, SAME_ROW, 0, NULL, (void*) &ToEndEvent, NULL, NULL, Button, N_(">>") },
2463 { 0, 0, 0, NULL, NULL, "", NULL, BoxEnd, "" },
2464 { 401, LR|TB, 401, NULL, (char*) &Exp, NULL, NULL, Graph, "shadow board" }, // board
2465   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[0], PopUp, "menuW" },
2466   { 2, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, pieceMenuStrings[1], PopUp, "menuB" },
2467   { -1, COMBO_CALLBACK, 0, NULL, (void*) &PMSelect, NULL, dropMenuStrings, PopUp, "menuD" },
2468 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2469 };
2470
2471 Option *
2472 LogoW (int n, int x, int y)
2473 {
2474     if(n == 10) DisplayLogos(&mainOptions[W_WHITE-1], NULL);
2475     return NULL;
2476 }
2477
2478 Option *
2479 LogoB (int n, int x, int y)
2480 {
2481     if(n == 10) DisplayLogos(NULL, &mainOptions[W_BLACK+1]);
2482     return NULL;
2483 }
2484
2485 void
2486 SizeKludge (int n)
2487 {   // callback called by GenericPopUp immediately after sizing the menu bar
2488     int width = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2489     int w = width - 44 - mainOptions[n].min;
2490     mainOptions[W_TITLE].max = w; // width left behind menu bar
2491     if(w < 0.4*width) // if no reasonable amount of space for title, force small layout
2492         mainOptions[W_SMALL].type = mainOptions[W_TITLE].type, mainOptions[W_TITLE].type = Skip;
2493 }
2494
2495 void
2496 MenuCallback (int n)
2497 {
2498     MenuProc *proc = (MenuProc *) (((MenuItem*)(mainOptions[n].choice))[values[n]].proc);
2499
2500     if(!proc) RecentEngineEvent(values[n] - firstEngineItem); else (proc)();
2501 }
2502
2503 static Option *
2504 Exp (int n, int x, int y)
2505 {
2506     static int but1, but3, oldW, oldH;
2507     int menuNr = -3, sizing, f, r;
2508     TimeMark now;
2509
2510     if(n == 0) { // motion
2511         if(SeekGraphClick(Press, x, y, 1)) return NULL;
2512         if((but1 || dragging == 2) && !PromoScroll(x, y)) DragPieceMove(x, y);
2513         if(but3) MovePV(x, y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
2514         if(appData.highlightDragging) {
2515             f = EventToSquare(x, BOARD_WIDTH);  if ( flipView && f >= 0) f = BOARD_WIDTH - 1 - f;
2516             r = EventToSquare(y, BOARD_HEIGHT); if (!flipView && r >= 0) r = BOARD_HEIGHT - 1 - r;
2517             HoverEvent(x, y, f, r);
2518         }
2519         return NULL;
2520     }
2521     if(n != 10 && PopDown(PromoDlg)) fromX = fromY = -1; // user starts fiddling with board when promotion dialog is up
2522     else GetTimeMark(&now);
2523     shiftKey = ShiftKeys();
2524     controlKey = (shiftKey & 0xC) != 0;
2525     shiftKey = (shiftKey & 3) != 0;
2526     switch(n) {
2527         case  1: LeftClick(Press,   x, y), but1 = 1; break;
2528         case -1: LeftClick(Release, x, y), but1 = 0; break;
2529         case  2: shiftKey = !shiftKey;
2530         case  3: menuNr = RightClick(Press,   x, y, &pmFromX, &pmFromY), but3 = 1; break;
2531         case -2: shiftKey = !shiftKey;
2532         case -3: menuNr = RightClick(Release, x, y, &pmFromX, &pmFromY), but3 = 0; break;
2533         case 10:
2534             sizing = (oldW != x || oldH != y);
2535             oldW = x; oldH = y;
2536             InitDrawingHandle(mainOptions + W_BOARD);
2537             if(sizing && SubtractTimeMarks(&now, &programStartTime) > 10000) return NULL; // don't redraw while sizing (except at startup)
2538             DrawPosition(True, NULL);
2539         default:
2540             return NULL;
2541     }
2542
2543     switch(menuNr) {
2544       case 0: return &mainOptions[shiftKey ? W_MENUW: W_MENUB];
2545       case 1: SetupDropMenu(); return &mainOptions[W_DROP];
2546       case 2:
2547       case -1: ErrorPopDown();
2548       case -2:
2549       default: break; // -3, so no clicks caught
2550     }
2551     return NULL;
2552 }
2553
2554 Option *
2555 BoardPopUp (int squareSize, int lineGap, void *clockFontThingy)
2556 {
2557     int i, size = BOARD_WIDTH*(squareSize + lineGap) + lineGap, logo = appData.logoSize;
2558     mainOptions[W_WHITE].choice = (char**) clockFontThingy;
2559     mainOptions[W_BLACK].choice = (char**) clockFontThingy;
2560     mainOptions[W_BOARD].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2561     mainOptions[W_BOARD].max = mainOptions[W_SMALL].max = size; // board size
2562     mainOptions[W_SMALL].max = size - 2; // board title (subtract border!)
2563     mainOptions[W_BLACK].max = mainOptions[W_WHITE].max = size/2-3; // clock width
2564     mainOptions[W_MESSG].max = appData.showButtonBar ? size-135 : size-2; // message
2565     mainOptions[W_MENU].max = size-40; // menu bar
2566     mainOptions[W_TITLE].type = appData.titleInWindow ? Label : Skip ;
2567     if(logo && logo <= size/4) { // Activate logos
2568         mainOptions[W_WHITE-1].type = mainOptions[W_BLACK+1].type = Graph;
2569         mainOptions[W_WHITE-1].max  = mainOptions[W_BLACK+1].max  = logo;
2570         mainOptions[W_WHITE-1].value= mainOptions[W_BLACK+1].value= logo/2;
2571         mainOptions[W_WHITE].min  |= SAME_ROW;
2572         mainOptions[W_WHITE].max  = mainOptions[W_BLACK].max  -= logo + 4;
2573         mainOptions[W_WHITE].name = mainOptions[W_BLACK].name = "Double\nHeight";
2574     }
2575     if(!appData.showButtonBar) for(i=W_BUTTON; i<W_BOARD; i++) mainOptions[i].type = Skip;
2576     for(i=0; i<8; i++) mainOptions[i+1].choice = (char**) menuBar[i].mi;
2577     AppendEnginesToMenu(appData.recentEngineList);
2578     GenericPopUp(mainOptions, "XBoard", BoardWindow, BoardWindow, NONMODAL, 1); // allways top-level
2579     return mainOptions;
2580 }
2581
2582 static Option *
2583 SlaveExp (int n, int x, int y)
2584 {
2585     if(n == 10) { // expose event
2586         flipView = !flipView; partnerUp = !partnerUp;
2587         DrawPosition(True, NULL); // [HGM] dual: draw other board in other orientation
2588         flipView = !flipView; partnerUp = !partnerUp;
2589     }
2590     return NULL;
2591 }
2592
2593 Option dualOptions[] = { // auxiliary board window
2594 { 0, L2L|T2T,              198, NULL, NULL, NULL, NULL, Label, "White" }, // white clock
2595 { 0, R2R|T2T|SAME_ROW,     198, NULL, NULL, NULL, NULL, Label, "Black" }, // black clock
2596 { 0, LR|T2T|BORDER,        401, NULL, NULL, NULL, NULL, Label, "This feature is experimental" }, // message field
2597 { 401, LR|TT, 401, NULL, (char*) &SlaveExp, NULL, NULL, Graph, "shadow board" }, // board
2598 { 0,  NO_OK, 0, NULL, NULL, "", NULL, EndMark , "" }
2599 };
2600
2601 void
2602 SlavePopUp ()
2603 {
2604     int size = BOARD_WIDTH*(squareSize + lineGap) + lineGap;
2605     // copy params from main board
2606     dualOptions[0].choice = mainOptions[W_WHITE].choice;
2607     dualOptions[1].choice = mainOptions[W_BLACK].choice;
2608     dualOptions[3].value = BOARD_HEIGHT*(squareSize + lineGap) + lineGap;
2609     dualOptions[3].max = dualOptions[2].max = size; // board width
2610     dualOptions[0].max = dualOptions[1].max = size/2 - 3; // clock width
2611     GenericPopUp(dualOptions, "XBoard", DummyDlg, BoardWindow, NONMODAL, appData.topLevel);
2612     SlaveResize(dualOptions+3);
2613 }
2614
2615 void
2616 DisplayWhiteClock (long timeRemaining, int highlight)
2617 {
2618     if(appData.noGUI) return;
2619     if(twoBoards && partnerUp) {
2620         DisplayTimerLabel(&dualOptions[0], _("White"), timeRemaining, highlight);
2621         return;
2622     }
2623     DisplayTimerLabel(&mainOptions[W_WHITE], _("White"), timeRemaining, highlight);
2624     if(highlight) SetClockIcon(0);
2625 }
2626
2627 void
2628 DisplayBlackClock (long timeRemaining, int highlight)
2629 {
2630     if(appData.noGUI) return;
2631     if(twoBoards && partnerUp) {
2632         DisplayTimerLabel(&dualOptions[1], _("Black"), timeRemaining, highlight);
2633         return;
2634     }
2635     DisplayTimerLabel(&mainOptions[W_BLACK], _("Black"), timeRemaining, highlight);
2636     if(highlight) SetClockIcon(1);
2637 }
2638
2639
2640 //---------------------------------------------
2641
2642 void
2643 DisplayMessage (char *message, char *extMessage)
2644 {
2645   /* display a message in the message widget */
2646
2647   char buf[MSG_SIZ];
2648
2649   if (extMessage)
2650     {
2651       if (*message)
2652         {
2653           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
2654           message = buf;
2655         }
2656       else
2657         {
2658           message = extMessage;
2659         };
2660     };
2661
2662     safeStrCpy(lastMsg, message, MSG_SIZ); // [HGM] make available
2663
2664   /* need to test if messageWidget already exists, since this function
2665      can also be called during the startup, if for example a Xresource
2666      is not set up correctly */
2667   if(mainOptions[W_MESSG].handle)
2668     SetWidgetLabel(&mainOptions[W_MESSG], message);
2669
2670   return;
2671 }
2672
2673 //----------------------------------- File Browser -------------------------------
2674
2675 #ifdef HAVE_DIRENT_H
2676 #include <dirent.h>
2677 #else
2678 #include <sys/dir.h>
2679 #define dirent direct
2680 #endif
2681
2682 #include <sys/stat.h>
2683
2684 #define MAXFILES 1000
2685
2686 static ChessProgramState *savCps;
2687 static FILE **savFP;
2688 static char *fileName, *extFilter, *savMode, **namePtr;
2689 static int folderPtr, filePtr, oldVal, byExtension, extFlag, pageStart, cnt;
2690 static char curDir[MSG_SIZ], title[MSG_SIZ], *folderList[MAXFILES], *fileList[MAXFILES];
2691
2692 static char *FileTypes[] = {
2693 "Chess Games",
2694 "Chess Positions",
2695 "Tournaments",
2696 "Opening Books",
2697 "Sound files",
2698 "Images",
2699 "Settings (*.ini)",
2700 "Log files",
2701 "All files",
2702 NULL,
2703 "PGN",
2704 "Old-Style Games",
2705 "FEN",
2706 "Old-Style Positions",
2707 NULL,
2708 NULL
2709 };
2710
2711 static char *Extensions[] = {
2712 ".pgn .game",
2713 ".fen .epd .pos",
2714 ".trn",
2715 ".bin",
2716 ".wav",
2717 ".ini",
2718 ".log",
2719 "",
2720 "INVALID",
2721 ".pgn",
2722 ".game",
2723 ".fen",
2724 ".pos",
2725 NULL,
2726 ""
2727 };
2728
2729 void DirSelProc P((int n, int sel));
2730 void FileSelProc P((int n, int sel));
2731 void SetTypeFilter P((int n));
2732 int BrowseOK P((int n));
2733 void Switch P((int n));
2734 void CreateDir P((int n));
2735
2736 Option browseOptions[] = {
2737 {   0,    LR|T2T,      500, NULL, NULL, NULL, NULL, Label, title },
2738 {   0,    L2L|T2T,     250, NULL, NULL, NULL, NULL, Label, N_("Directories:") },
2739 {   0,R2R|T2T|SAME_ROW,100, NULL, NULL, NULL, NULL, Label, N_("Files:") },
2740 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by name") },
2741 {   0, R2R|TT|SAME_ROW, 70, NULL, (void*) &Switch, NULL, NULL, Button, N_("by type") },
2742 { 300,    L2L|TB,      250, NULL, (void*) folderList, (char*) &DirSelProc, NULL, ListBox, "" },
2743 { 300, R2R|TB|SAME_ROW,250, NULL, (void*) fileList, (char*) &FileSelProc, NULL, ListBox, "" },
2744 {   0,       0,        300, NULL, (void*) &fileName, NULL, NULL, TextBox, N_("Filename:") },
2745 {   0,    SAME_ROW,    120, NULL, (void*) &CreateDir, NULL, NULL, Button, N_("New directory") },
2746 {   0, COMBO_CALLBACK, 150, NULL, (void*) &SetTypeFilter, NULL, FileTypes, ComboBox, N_("File type:") },
2747 {   0,    SAME_ROW,      0, NULL, (void*) &BrowseOK, "", NULL, EndMark , "" }
2748 };
2749
2750 int
2751 BrowseOK (int n)
2752 {
2753         if(!fileName[0]) { // it is enough to have a file selected
2754             if(browseOptions[6].textValue) { // kludge: if callback specified we browse for file
2755                 int sel = SelectedListBoxItem(&browseOptions[6]);
2756                 if(sel < 0 || sel >= filePtr) return FALSE;
2757                 ASSIGN(fileName, fileList[sel]);
2758             } else { // we browse for path
2759                 ASSIGN(fileName, curDir); // kludge: without callback we browse for path
2760             }
2761         }
2762         if(!fileName[0]) return FALSE; // refuse OK when no file
2763         if(!savMode[0]) { // browsing for name only (dialog Browse button)
2764                 if(fileName[0] == '/') // We already had a path name
2765                     snprintf(title, MSG_SIZ, "%s", fileName);
2766                 else
2767                     snprintf(title, MSG_SIZ, "%s/%s", curDir, fileName);
2768                 SetWidgetText((Option*) savFP, title, TransientDlg);
2769                 currentCps = savCps; // could return to Engine Settings dialog!
2770                 return TRUE;
2771         }
2772         *savFP = fopen(fileName, savMode);
2773         if(*savFP == NULL) return FALSE; // refuse OK if file not openable
2774         ASSIGN(*namePtr, fileName);
2775         ScheduleDelayedEvent(DelayedLoad, 50);
2776         currentCps = savCps; // not sure this is ever non-null
2777         return TRUE;
2778 }
2779
2780 int
2781 AlphaNumCompare (char *p, char *q)
2782 {
2783     while(*p) {
2784         if(isdigit(*p) && isdigit(*q) && atoi(p) != atoi(q))
2785              return (atoi(p) > atoi(q) ? 1 : -1);
2786         if(*p != *q) break;
2787         p++, q++;
2788     }
2789     if(*p == *q) return 0;
2790     return (*p > *q ? 1 : -1);
2791 }
2792
2793 int
2794 Comp (const void *s, const void *t)
2795 {
2796     char *p = *(char**) s, *q = *(char**) t;
2797     if(extFlag) {
2798         char *h; int r;
2799         while(h = strchr(p, '.')) p = h+1;
2800         if(p == *(char**) s) p = "";
2801         while(h = strchr(q, '.')) q = h+1;
2802         if(q == *(char**) t) q = "";
2803         r = AlphaNumCompare(p, q);
2804         if(r) return r;
2805     }
2806     return AlphaNumCompare( *(char**) s, *(char**) t );
2807 }
2808
2809 void
2810 ListDir (int pathFlag)
2811 {
2812         DIR *dir;
2813         struct dirent *dp;
2814         struct stat statBuf;
2815         static int lastFlag;
2816
2817         if(pathFlag < 0) pathFlag = lastFlag;
2818         lastFlag = pathFlag;
2819         dir = opendir(".");
2820         getcwd(curDir, MSG_SIZ);
2821         snprintf(title, MSG_SIZ, "%s   %s", _("Contents of"), curDir);
2822         folderPtr = filePtr = cnt = 0; // clear listing
2823
2824         while (dp = readdir(dir)) { // pass 1: list foders
2825             char *s = dp->d_name;
2826             if(!stat(s, &statBuf) && S_ISDIR(statBuf.st_mode)) { // stat succeeds and tells us it is directory
2827                 if(s[0] == '.' && strcmp(s, "..")) continue; // suppress hidden, except ".."
2828                 ASSIGN(folderList[folderPtr], s); if(folderPtr < MAXFILES-2) folderPtr++;
2829             } else if(!pathFlag) {
2830                 char *s = dp->d_name, match=0;
2831 //              if(cnt == pageStart) { ASSIGN }
2832                 if(s[0] == '.') continue; // suppress hidden files
2833                 if(extFilter[0]) { // [HGM] filter on extension
2834                     char *p = extFilter, *q;
2835                     do {
2836                         if(q = strchr(p, ' ')) *q = 0;
2837                         if(strstr(s, p)) match++;
2838                         if(q) *q = ' ';
2839                     } while(q && (p = q+1));
2840                     if(!match) continue;
2841                 }
2842                 if(filePtr == MAXFILES-2) continue;
2843                 if(cnt++ < pageStart) continue;
2844                 ASSIGN(fileList[filePtr], s); filePtr++;
2845             }
2846         }
2847         if(filePtr == MAXFILES-2) { ASSIGN(fileList[filePtr], _("  next page")); filePtr++; }
2848         FREE(folderList[folderPtr]); folderList[folderPtr] = NULL;
2849         FREE(fileList[filePtr]); fileList[filePtr] = NULL;
2850         closedir(dir);
2851         extFlag = 0;         qsort((void*)folderList, folderPtr, sizeof(char*), &Comp);
2852         extFlag = byExtension; qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
2853 }
2854
2855 void
2856 Refresh (int pathFlag)
2857 {
2858     ListDir(pathFlag); // and make new one
2859     LoadListBox(&browseOptions[5], "", -1, -1);
2860     LoadListBox(&browseOptions[6], "", -1, -1);
2861     SetWidgetLabel(&browseOptions[0], title);
2862 }
2863
2864 static char msg1[] = N_("FIRST TYPE DIRECTORY NAME HERE");
2865 static char msg2[] = N_("TRY ANOTHER NAME");
2866
2867 void
2868 CreateDir (int n)
2869 {
2870     char *name, *errmsg = "";
2871     GetWidgetText(&browseOptions[n-1], &name);
2872     if(!strcmp(name, msg1) || !strcmp(name, msg2)) return;
2873     if(!name[0]) errmsg = _(msg1); else
2874     if(mkdir(name, 0755)) errmsg = _(msg2);
2875     else {
2876         chdir(name);
2877         Refresh(-1);
2878     }
2879     SetWidgetText(&browseOptions[n-1], errmsg, BrowserDlg);
2880 }
2881
2882 void
2883 Switch (int n)
2884 {
2885     if(byExtension == (n == 4)) return;
2886     extFlag = byExtension = (n == 4);
2887     qsort((void*)fileList, filePtr < MAXFILES-2 ? filePtr : MAXFILES-2, sizeof(char*), &Comp);
2888     LoadListBox(&browseOptions[6], "", -1, -1);
2889 }
2890
2891 void
2892 SetTypeFilter (int n)
2893 {
2894     int j = values[n];
2895     if(j == browseOptions[n].value) return; // no change
2896     browseOptions[n].value = j;
2897     SetWidgetLabel(&browseOptions[n], FileTypes[j]);
2898     ASSIGN(extFilter, Extensions[j]);
2899     pageStart = 0;
2900     Refresh(-1); // uses pathflag remembered by ListDir
2901     values[n] = oldVal; // do not disturb combo settings of underlying dialog
2902 }
2903
2904 void
2905 FileSelProc (int n, int sel)
2906 {
2907     if(sel < 0 || fileList[sel] == NULL) return;
2908     if(sel == MAXFILES-2) { pageStart = cnt; Refresh(-1); return; }
2909     ASSIGN(fileName, fileList[sel]);
2910     if(BrowseOK(0)) PopDown(BrowserDlg);
2911 }
2912
2913 void
2914 DirSelProc (int n, int sel)
2915 {
2916     if(!chdir(folderList[sel])) { // cd succeeded, so we are in new directory now
2917         Refresh(-1);
2918     }
2919 }
2920
2921 void
2922 Browse (DialogClass dlg, char *label, char *proposed, char *ext, Boolean pathFlag, char *mode, char **name, FILE **fp)
2923 {
2924     int j=0;
2925     savFP = fp; savMode = mode, namePtr = name, savCps = currentCps, oldVal = values[9]; // save params, for use in callback
2926     ASSIGN(extFilter, ext);
2927     ASSIGN(fileName, proposed ? proposed : "");
2928     for(j=0; Extensions[j]; j++) // look up actual value in list of possible values, to get selection nr
2929         if(extFilter && !strcmp(extFilter, Extensions[j])) break;
2930     if(Extensions[j] == NULL) { j++; ASSIGN(FileTypes[j], extFilter); }
2931     browseOptions[9].value = j;
2932     browseOptions[6].textValue = (char*) (pathFlag ? NULL : &FileSelProc); // disable file listbox during path browsing
2933     pageStart = 0; ListDir(pathFlag);
2934     currentCps = NULL;
2935     GenericPopUp(browseOptions, label, BrowserDlg, dlg, MODAL, 0);
2936     SetWidgetLabel(&browseOptions[9], FileTypes[j]);
2937 }
2938
2939 static char *openName;
2940 FileProc fileProc;
2941 char *fileOpenMode;
2942 FILE *openFP;
2943
2944 void
2945 DelayedLoad ()
2946 {
2947   (void) (*fileProc)(openFP, 0, openName);
2948 }
2949
2950 void
2951 FileNamePopUp (char *label, char *def, char *filter, FileProc proc, char *openMode)
2952 {
2953     fileProc = proc;            /* I can't see a way not */
2954     fileOpenMode = openMode;    /*   to use globals here */
2955     FileNamePopUpWrapper(label, def, filter, proc, False, openMode, &openName, &openFP);
2956 }
2957
2958 void
2959 ActivateTheme (int col)
2960 {
2961     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap; else lineGap = defaultLineGap;
2962     InitDrawingParams(strcmp(oldPieceDir, appData.pieceDirectory));
2963     InitDrawingSizes(-1, 0);
2964     DrawPosition(True, NULL);
2965 }
2966