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