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