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