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