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