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