Implement kifu move disambiguation
[xboard.git] / xaw / xboard.c
1 /*
2  * xboard.c -- X front end for XBoard
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
9  *
10  * The following terms apply to Digital Equipment Corporation's copyright
11  * interest in XBoard:
12  * ------------------------------------------------------------------------
13  * All Rights Reserved
14  *
15  * Permission to use, copy, modify, and distribute this software and its
16  * documentation for any purpose and without fee is hereby granted,
17  * provided that the above copyright notice appear in all copies and that
18  * both that copyright notice and this permission notice appear in
19  * supporting documentation, and that the name of Digital not be
20  * used in advertising or publicity pertaining to distribution of the
21  * software without specific, written prior permission.
22  *
23  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
24  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
25  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
26  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
27  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
28  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
29  * SOFTWARE.
30  * ------------------------------------------------------------------------
31  *
32  * The following terms apply to the enhanced version of XBoard
33  * distributed by the Free Software Foundation:
34  * ------------------------------------------------------------------------
35  *
36  * GNU XBoard is free software: you can redistribute it and/or modify
37  * it under the terms of the GNU General Public License as published by
38  * the Free Software Foundation, either version 3 of the License, or (at
39  * your option) any later version.
40  *
41  * GNU XBoard is distributed in the hope that it will be useful, but
42  * WITHOUT ANY WARRANTY; without even the implied warranty of
43  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
44  * General Public License for more details.
45  *
46  * You should have received a copy of the GNU General Public License
47  * along with this program. If not, see http://www.gnu.org/licenses/.  *
48  *
49  *------------------------------------------------------------------------
50  ** See the file ChangeLog for a revision history.  */
51
52 #define HIGHDRAG 1
53
54 #include "config.h"
55
56 #include <stdio.h>
57 #include <ctype.h>
58 #include <signal.h>
59 #include <errno.h>
60 #include <sys/types.h>
61 #include <sys/stat.h>
62 #include <pwd.h>
63 #include <math.h>
64 #include <cairo/cairo.h>
65 #include <cairo/cairo-xlib.h>
66
67 #if !OMIT_SOCKETS
68 # if HAVE_SYS_SOCKET_H
69 #  include <sys/socket.h>
70 #  include <netinet/in.h>
71 #  include <netdb.h>
72 # else /* not HAVE_SYS_SOCKET_H */
73 #  if HAVE_LAN_SOCKET_H
74 #   include <lan/socket.h>
75 #   include <lan/in.h>
76 #   include <lan/netdb.h>
77 #  else /* not HAVE_LAN_SOCKET_H */
78 #   define OMIT_SOCKETS 1
79 #  endif /* not HAVE_LAN_SOCKET_H */
80 # endif /* not HAVE_SYS_SOCKET_H */
81 #endif /* !OMIT_SOCKETS */
82
83 #if STDC_HEADERS
84 # include <stdlib.h>
85 # include <string.h>
86 #else /* not STDC_HEADERS */
87 extern char *getenv();
88 # if HAVE_STRING_H
89 #  include <string.h>
90 # else /* not HAVE_STRING_H */
91 #  include <strings.h>
92 # endif /* not HAVE_STRING_H */
93 #endif /* not STDC_HEADERS */
94
95 #if HAVE_SYS_FCNTL_H
96 # include <sys/fcntl.h>
97 #else /* not HAVE_SYS_FCNTL_H */
98 # if HAVE_FCNTL_H
99 #  include <fcntl.h>
100 # endif /* HAVE_FCNTL_H */
101 #endif /* not HAVE_SYS_FCNTL_H */
102
103 #if HAVE_SYS_SYSTEMINFO_H
104 # include <sys/systeminfo.h>
105 #endif /* HAVE_SYS_SYSTEMINFO_H */
106
107 #if TIME_WITH_SYS_TIME
108 # include <sys/time.h>
109 # include <time.h>
110 #else
111 # if HAVE_SYS_TIME_H
112 #  include <sys/time.h>
113 # else
114 #  include <time.h>
115 # endif
116 #endif
117
118 #if HAVE_UNISTD_H
119 # include <unistd.h>
120 #endif
121
122 #if HAVE_SYS_WAIT_H
123 # include <sys/wait.h>
124 #endif
125
126 #if HAVE_DIRENT_H
127 # include <dirent.h>
128 # define NAMLEN(dirent) strlen((dirent)->d_name)
129 # define HAVE_DIR_STRUCT
130 #else
131 # define dirent direct
132 # define NAMLEN(dirent) (dirent)->d_namlen
133 # if HAVE_SYS_NDIR_H
134 #  include <sys/ndir.h>
135 #  define HAVE_DIR_STRUCT
136 # endif
137 # if HAVE_SYS_DIR_H
138 #  include <sys/dir.h>
139 #  define HAVE_DIR_STRUCT
140 # endif
141 # if HAVE_NDIR_H
142 #  include <ndir.h>
143 #  define HAVE_DIR_STRUCT
144 # endif
145 #endif
146
147 #if ENABLE_NLS
148 #include <locale.h>
149 #endif
150
151 #include <X11/Intrinsic.h>
152 #include <X11/StringDefs.h>
153 #include <X11/Shell.h>
154 #include <X11/cursorfont.h>
155 #include <X11/Xatom.h>
156 #include <X11/Xmu/Atoms.h>
157 #if USE_XAW3D
158 #include <X11/Xaw3d/Dialog.h>
159 #include <X11/Xaw3d/Form.h>
160 #include <X11/Xaw3d/List.h>
161 #include <X11/Xaw3d/Label.h>
162 #include <X11/Xaw3d/SimpleMenu.h>
163 #include <X11/Xaw3d/SmeBSB.h>
164 #include <X11/Xaw3d/SmeLine.h>
165 #include <X11/Xaw3d/Box.h>
166 #include <X11/Xaw3d/MenuButton.h>
167 #include <X11/Xaw3d/Text.h>
168 #include <X11/Xaw3d/AsciiText.h>
169 #else
170 #include <X11/Xaw/Dialog.h>
171 #include <X11/Xaw/Form.h>
172 #include <X11/Xaw/List.h>
173 #include <X11/Xaw/Label.h>
174 #include <X11/Xaw/SimpleMenu.h>
175 #include <X11/Xaw/SmeBSB.h>
176 #include <X11/Xaw/SmeLine.h>
177 #include <X11/Xaw/Box.h>
178 #include <X11/Xaw/MenuButton.h>
179 #include <X11/Xaw/Text.h>
180 #include <X11/Xaw/AsciiText.h>
181 #endif
182
183 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
184 #include "common.h"
185
186 #include "bitmaps/icon_white.bm"
187 #include "bitmaps/icon_black.bm"
188 #include "bitmaps/checkmark.bm"
189
190 #include "frontend.h"
191 #include "backend.h"
192 #include "backendz.h"
193 #include "moves.h"
194 #include "xboard.h"
195 #include "childio.h"
196 #include "xgamelist.h"
197 #include "xhistory.h"
198 #include "menus.h"
199 #include "board.h"
200 #include "dialogs.h"
201 #include "engineoutput.h"
202 #include "usystem.h"
203 #include "gettext.h"
204 #include "draw.h"
205
206 #define SLASH '/'
207
208 #ifdef __EMX__
209 #ifndef HAVE_USLEEP
210 #define HAVE_USLEEP
211 #endif
212 #define usleep(t)   _sleep2(((t)+500)/1000)
213 #endif
214
215 #ifdef ENABLE_NLS
216 # define  _(s) gettext (s)
217 # define N_(s) gettext_noop (s)
218 #else
219 # define  _(s) (s)
220 # define N_(s)  s
221 #endif
222
223 int main P((int argc, char **argv));
224 RETSIGTYPE CmailSigHandler P((int sig));
225 RETSIGTYPE IntSigHandler P((int sig));
226 RETSIGTYPE TermSizeSigHandler P((int sig));
227 Widget CreateMenuBar P((Menu *mb, int boardWidth));
228 #if ENABLE_NLS
229 char *InsertPxlSize P((char *pattern, int targetPxlSize));
230 XFontSet CreateFontSet P((char *base_fnt_lst));
231 #else
232 char *FindFont P((char *pattern, int targetPxlSize));
233 #endif
234 void ReadBitmap P((Pixmap *pm, String name, unsigned char bits[],
235                    u_int wreq, u_int hreq));
236 void EventProc P((Widget widget, caddr_t unused, XEvent *event));
237 void DelayedDrag P((void));
238 static void MoveTypeInProc P((Widget widget, caddr_t unused, XEvent *event));
239 void HandlePV P((Widget w, XEvent * event,
240                      String * params, Cardinal * nParams));
241 void DrawPositionProc P((Widget w, XEvent *event,
242                      String *prms, Cardinal *nprms));
243 void CommentClick P((Widget w, XEvent * event,
244                    String * params, Cardinal * nParams));
245 void ICSInputBoxPopUp P((void));
246 void SelectCommand P((Widget w, XtPointer client_data, XtPointer call_data));
247 void KeyBindingProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
248 void QuitWrapper P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
249 static void EnterKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
250 static void UpKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
251 static void DownKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
252 void TempBackwardProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
253 void TempForwardProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
254 Boolean TempBackwardActive = False;
255 void ManInner P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
256 void DisplayMove P((int moveNumber));
257 void update_ics_width P(());
258 int CopyMemoProc P(());
259 static int FindLogo P((char *place, char *name, char *buf));
260
261 /*
262 * XBoard depends on Xt R4 or higher
263 */
264 int xtVersion = XtSpecificationRelease;
265
266 int xScreen;
267 Display *xDisplay;
268 Window xBoardWindow;
269 Pixel lowTimeWarningColor, dialogColor, buttonColor; // used in widgets
270 Pixmap iconPixmap, wIconPixmap, bIconPixmap, xMarkPixmap;
271 Widget shellWidget, formWidget, boardWidget, titleWidget, dropMenu, menuBarWidget;
272 Option *optList; // contains all widgets of main window
273 #if ENABLE_NLS
274 XFontSet fontSet, clockFontSet;
275 #else
276 Font clockFontID;
277 XFontStruct *clockFontStruct;
278 #endif
279 Font coordFontID, countFontID;
280 XFontStruct *coordFontStruct, *countFontStruct;
281 XtAppContext appContext;
282 char *layoutName;
283
284 char installDir[] = "."; // [HGM] UCI: needed for UCI; probably needs run-time initializtion
285
286 Position commentX = -1, commentY = -1;
287 Dimension commentW, commentH;
288 typedef unsigned int BoardSize;
289 BoardSize boardSize;
290 Boolean chessProgram;
291
292 int  minX, minY; // [HGM] placement: volatile limits on upper-left corner
293 int smallLayout = 0, tinyLayout = 0,
294   marginW, marginH, // [HGM] for run-time resizing
295   fromX = -1, fromY = -1, toX, toY, commentUp = False,
296   errorExitStatus = -1, defaultLineGap;
297 Dimension textHeight;
298 Pixel timerForegroundPixel, timerBackgroundPixel;
299 Pixel buttonForegroundPixel, buttonBackgroundPixel;
300 char *chessDir, *programName, *programVersion;
301 Boolean alwaysOnTop = False;
302 char *icsTextMenuString;
303 char *icsNames;
304 char *firstChessProgramNames;
305 char *secondChessProgramNames;
306
307 WindowPlacement wpMain;
308 WindowPlacement wpConsole;
309 WindowPlacement wpComment;
310 WindowPlacement wpMoveHistory;
311 WindowPlacement wpEvalGraph;
312 WindowPlacement wpEngineOutput;
313 WindowPlacement wpGameList;
314 WindowPlacement wpTags;
315 WindowPlacement wpDualBoard;
316
317
318 /* This magic number is the number of intermediate frames used
319    in each half of the animation. For short moves it's reduced
320    by 1. The total number of frames will be factor * 2 + 1.  */
321 #define kFactor    4
322
323 SizeDefaults sizeDefaults[] = SIZE_DEFAULTS;
324
325 typedef struct {
326     char piece;
327     char* widget;
328 } DropMenuEnables;
329
330 DropMenuEnables dmEnables[] = {
331     { 'P', "Pawn" },
332     { 'N', "Knight" },
333     { 'B', "Bishop" },
334     { 'R', "Rook" },
335     { 'Q', "Queen" }
336 };
337
338 Arg shellArgs[] = {
339     { XtNwidth, 0 },
340     { XtNheight, 0 },
341     { XtNminWidth, 0 },
342     { XtNminHeight, 0 },
343     { XtNmaxWidth, 0 },
344     { XtNmaxHeight, 0 }
345 };
346
347 XtResource clientResources[] = {
348     { "flashCount", "flashCount", XtRInt, sizeof(int),
349         XtOffset(AppDataPtr, flashCount), XtRImmediate,
350         (XtPointer) FLASH_COUNT  },
351 };
352
353 XrmOptionDescRec shellOptions[] = {
354     { "-flashCount", "flashCount", XrmoptionSepArg, NULL },
355     { "-flash", "flashCount", XrmoptionNoArg, "3" },
356     { "-xflash", "flashCount", XrmoptionNoArg, "0" },
357 };
358
359 XtActionsRec boardActions[] = {
360     { "DrawPosition", DrawPositionProc },
361     { "HandlePV", HandlePV },
362     { "SelectPV", SelectPV },
363     { "StopPV", StopPV },
364     { "MenuItem", KeyBindingProc }, // [HGM] generic handler for key bindings
365     { "QuitProc", QuitWrapper },
366     { "ManProc", ManInner },
367     { "TempBackwardProc", TempBackwardProc },
368     { "TempForwardProc", TempForwardProc },
369     { "CommentClick", (XtActionProc) CommentClick },
370     { "GenericPopDown", (XtActionProc) GenericPopDown },
371     { "ErrorPopDown", (XtActionProc) ErrorPopDown },
372     { "CopyMemoProc", (XtActionProc) CopyMemoProc },
373     { "SelectMove", (XtActionProc) SelectMoveX },
374     { "LoadSelectedProc", LoadSelectedProc },
375     { "SetFilterProc", SetFilterProc },
376     { "TypeInProc", TypeInProc },
377     { "EnterKeyProc", EnterKeyProc },
378     { "UpKeyProc", UpKeyProc },
379     { "DownKeyProc", DownKeyProc },
380     { "WheelProc", WheelProc },
381     { "TabProc", TabProc },
382 };
383
384 char globalTranslations[] =
385   ":Meta<Key>Next: MenuItem(LoadNextGameProc) \n \
386    :Meta<Key>Prior: MenuItem(LoadPrevGameProc) \n \
387    :Ctrl<Key>Down: LoadSelectedProc(3) \n \
388    :Ctrl<Key>Up: LoadSelectedProc(-3) \n \
389    :Shift<Key>Next: MenuItem(LoadNextPositionProc) \n \
390    :Shift<Key>Prior: MenuItem(LoadPrevPositionProc) \n \
391    :<Key>Pause: MenuItem(Mode.Pause) \n \
392    :Ctrl<Key>d: MenuItem(DebugProc) \n \
393    :Meta Ctrl<Key>F12: MenuItem(DebugProc) \n \
394    :<Key>Left: MenuItem(Edit.Backward) \n \
395    :<Key>Right: MenuItem(Edit.Forward) \n \
396    :Ctrl<Key>P: MenuItem(PonderNextMove) \n "
397 #ifndef OPTIONSDIALOG
398     "\
399    :Ctrl<Key>Q: MenuItem(AlwaysQueenProc) \n \
400    :Ctrl<Key>F: MenuItem(AutoflagProc) \n \
401    :Ctrl<Key>A: MenuItem(AnimateMovingProc) \n \
402    :Ctrl<Key>L: MenuItem(TestLegalityProc) \n \
403    :Ctrl<Key>H: MenuItem(HideThinkingProc) \n "
404 #endif
405    "\
406    :<KeyDown>Return: TempBackwardProc() \n \
407    :<KeyUp>Return: TempForwardProc() \n";
408
409 char ICSInputTranslations[] =
410     "<Key>Up: UpKeyProc() \n "
411     "<Key>Down: DownKeyProc() \n "
412     "<Key>Return: EnterKeyProc() \n";
413
414 // [HGM] vari: another hideous kludge: call extend-end first so we can be sure select-start works,
415 //             as the widget is destroyed before the up-click can call extend-end
416 char commentTranslations[] = "<Btn3Down>: extend-end(PRIMARY) select-start() CommentClick() \n";
417
418 String xboardResources[] = {
419     "*Error*translations: #override\\n <Key>Return: ErrorPopDown()",
420     NULL
421   };
422
423
424 /* Max possible square size */
425 #define MAXSQSIZE 256
426
427 /* Arrange to catch delete-window events */
428 Atom wm_delete_window;
429 void
430 CatchDeleteWindow (Widget w, String procname)
431 {
432   char buf[MSG_SIZ];
433   XSetWMProtocols(xDisplay, XtWindow(w), &wm_delete_window, 1);
434   snprintf(buf, sizeof(buf), "<Message>WM_PROTOCOLS: %s() \n", procname);
435   XtAugmentTranslations(w, XtParseTranslationTable(buf));
436 }
437
438 void
439 BoardToTop ()
440 {
441   Arg args[16];
442   XtSetArg(args[0], XtNiconic, False);
443   XtSetValues(shellWidget, args, 1);
444
445   XtPopup(shellWidget, XtGrabNone); /* Raise if lowered  */
446 }
447
448 //---------------------------------------------------------------------------------------------------------
449 // some symbol definitions to provide the proper (= XBoard) context for the code in args.h
450 #define XBOARD True
451 #define JAWS_ARGS
452 #define CW_USEDEFAULT (1<<31)
453 #define ICS_TEXT_MENU_SIZE 90
454 #define DEBUG_FILE "xboard.debug"
455 #define SetCurrentDirectory chdir
456 #define GetCurrentDirectory(SIZE, NAME) getcwd(NAME, SIZE)
457 #define OPTCHAR "-"
458 #define SEPCHAR " "
459
460 // The option definition and parsing code common to XBoard and WinBoard is collected in this file
461 #include "args.h"
462
463 // front-end part of option handling
464
465 // [HGM] This platform-dependent table provides the location for storing the color info
466 extern char *crWhite, * crBlack;
467
468 void *
469 colorVariable[] = {
470   &appData.whitePieceColor,
471   &appData.blackPieceColor,
472   &appData.lightSquareColor,
473   &appData.darkSquareColor,
474   &appData.highlightSquareColor,
475   &appData.premoveHighlightColor,
476   &appData.lowTimeWarningColor,
477   NULL,
478   NULL,
479   NULL,
480   NULL,
481   NULL,
482   &crWhite,
483   &crBlack,
484   NULL
485 };
486
487 // [HGM] font: keep a font for each square size, even non-stndard ones
488 #define NUM_SIZES 18
489 #define MAX_SIZE 130
490 Boolean fontIsSet[NUM_FONTS], fontValid[NUM_FONTS][MAX_SIZE];
491 char *fontTable[NUM_FONTS][MAX_SIZE];
492
493 void
494 ParseFont (char *name, int number)
495 { // in XBoard, only 2 of the fonts are currently implemented, and we just copy their name
496   int size;
497   if(sscanf(name, "size%d:", &size)) {
498     // [HGM] font: font is meant for specific boardSize (likely from settings file);
499     //       defer processing it until we know if it matches our board size
500     if(strstr(name, "-*-") &&        // only pay attention to things that look like X-fonts
501        size >= 0 && size<MAX_SIZE) { // for now, fixed limit
502         fontTable[number][size] = strdup(strchr(name, ':')+1);
503         fontValid[number][size] = True;
504     }
505     return;
506   }
507   switch(number) {
508     case 0: // CLOCK_FONT
509         appData.clockFont = strdup(name);
510       break;
511     case 1: // MESSAGE_FONT
512         appData.font = strdup(name);
513       break;
514     case 2: // COORD_FONT
515         appData.coordFont = strdup(name);
516       break;
517     default:
518       return;
519   }
520   fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
521 }
522
523 void
524 SetFontDefaults ()
525 { // only 2 fonts currently
526   appData.clockFont = CLOCK_FONT_NAME;
527   appData.coordFont = COORD_FONT_NAME;
528   appData.font  =   DEFAULT_FONT_NAME;
529 }
530
531 void
532 CreateFonts ()
533 { // no-op, until we identify the code for this already in XBoard and move it here
534 }
535
536 void
537 ParseColor (int n, char *name)
538 { // in XBoard, just copy the color-name string
539   if(colorVariable[n] && *name == '#') *(char**)colorVariable[n] = strdup(name);
540 }
541
542 char *
543 Col2Text (int n)
544 {
545     return *(char**)colorVariable[n];
546 }
547
548 void
549 ParseTextAttribs (ColorClass cc, char *s)
550 {
551     (&appData.colorShout)[cc] = strdup(s);
552 }
553
554 void
555 ParseBoardSize (void *addr, char *name)
556 {
557     appData.boardSize = strdup(name);
558 }
559
560 void
561 LoadAllSounds ()
562 { // In XBoard the sound-playing program takes care of obtaining the actual sound
563 }
564
565 void
566 SetCommPortDefaults ()
567 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
568 }
569
570 // [HGM] args: these three cases taken out to stay in front-end
571 void
572 SaveFontArg (FILE *f, ArgDescriptor *ad)
573 {
574   char *name;
575   int i, n = (int)(intptr_t)ad->argLoc;
576   switch(n) {
577     case 0: // CLOCK_FONT
578         name = appData.clockFont;
579       break;
580     case 1: // MESSAGE_FONT
581         name = appData.font;
582       break;
583     case 2: // COORD_FONT
584         name = appData.coordFont;
585       break;
586     default:
587       return;
588   }
589   for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
590     if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
591         fontTable[n][squareSize] = strdup(name);
592         fontValid[n][squareSize] = True;
593         break;
594   }
595   for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
596     fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
597 }
598
599 void
600 ExportSounds ()
601 { // nothing to do, as the sounds are at all times represented by their text-string names already
602 }
603
604 void
605 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
606 {       // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
607         fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
608 }
609
610 void
611 SaveColor (FILE *f, ArgDescriptor *ad)
612 {       // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
613         if(colorVariable[(int)(intptr_t)ad->argLoc])
614         fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
615 }
616
617 void
618 SaveBoardSize (FILE *f, char *name, void *addr)
619 { // wrapper to shield back-end from BoardSize & sizeInfo
620   fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
621 }
622
623 void
624 ParseCommPortSettings (char *s)
625 { // no such option in XBoard (yet)
626 }
627
628 int frameX, frameY;
629
630 void
631 GetActualPlacement (Widget wg, WindowPlacement *wp)
632 {
633   XWindowAttributes winAt;
634   Window win, dummy;
635   int rx, ry;
636
637   if(!wg) return;
638
639   win = XtWindow(wg);
640   XGetWindowAttributes(xDisplay, win, &winAt); // this works, where XtGetValues on XtNx, XtNy does not!
641   XTranslateCoordinates (xDisplay, win, winAt.root, -winAt.border_width, -winAt.border_width, &rx, &ry, &dummy);
642   wp->x = rx - winAt.x;
643   wp->y = ry - winAt.y;
644   wp->height = winAt.height;
645   wp->width = winAt.width;
646   frameX = winAt.x; frameY = winAt.y; // remember to decide if windows touch
647 }
648
649 void
650 GetPlacement (DialogClass dlg, WindowPlacement *wp)
651 { // wrapper to shield back-end from widget type
652   if(shellUp[dlg]) GetActualPlacement(shells[dlg], wp);
653 }
654
655 void
656 GetWindowCoords ()
657 { // wrapper to shield use of window handles from back-end (make addressible by number?)
658   // In XBoard this will have to wait until awareness of window parameters is implemented
659   GetActualPlacement(shellWidget, &wpMain);
660   if(shellUp[EngOutDlg]) GetActualPlacement(shells[EngOutDlg], &wpEngineOutput);
661   if(shellUp[HistoryDlg]) GetActualPlacement(shells[HistoryDlg], &wpMoveHistory);
662   if(shellUp[EvalGraphDlg]) GetActualPlacement(shells[EvalGraphDlg], &wpEvalGraph);
663   if(shellUp[GameListDlg]) GetActualPlacement(shells[GameListDlg], &wpGameList);
664   if(shellUp[CommentDlg]) GetActualPlacement(shells[CommentDlg], &wpComment);
665   if(shellUp[TagsDlg]) GetActualPlacement(shells[TagsDlg], &wpTags);
666 }
667
668 void
669 PrintCommPortSettings (FILE *f, char *name)
670 { // This option does not exist in XBoard
671 }
672
673 void
674 EnsureOnScreen (int *x, int *y, int minX, int minY)
675 {
676   return;
677 }
678
679 int
680 MainWindowUp ()
681 { // [HGM] args: allows testing if main window is realized from back-end
682   return xBoardWindow != 0;
683 }
684
685 void
686 PopUpStartupDialog ()
687 {  // start menu not implemented in XBoard
688 }
689
690 char *
691 ConvertToLine (int argc, char **argv)
692 {
693   static char line[128*1024], buf[1024];
694   int i;
695
696   line[0] = NULLCHAR;
697   for(i=1; i<argc; i++)
698     {
699       if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
700           && argv[i][0] != '{' )
701         snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
702       else
703         snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
704       strncat(line, buf, 128*1024 - strlen(line) - 1 );
705     }
706
707   line[strlen(line)-1] = NULLCHAR;
708   return line;
709 }
710
711 //--------------------------------------------------------------------------------------------
712
713 void
714 ResizeBoardWindow (int w, int h, int inhibit)
715 {
716     w += marginW + 1; // [HGM] not sure why the +1 is (sometimes) needed...
717     h += marginH;
718     shellArgs[0].value = w;
719     shellArgs[1].value = h;
720     shellArgs[4].value = shellArgs[2].value = w;
721     shellArgs[5].value = shellArgs[3].value = h;
722     XtSetValues(shellWidget, &shellArgs[0], inhibit ? 6 : 2);
723
724     XSync(xDisplay, False);
725 }
726
727 static int
728 MakeOneColor (char *name, Pixel *color)
729 {
730     XrmValue vFrom, vTo;
731     if (!appData.monoMode) {
732         vFrom.addr = (caddr_t) name;
733         vFrom.size = strlen(name);
734         XtConvert(shellWidget, XtRString, &vFrom, XtRPixel, &vTo);
735         if (vTo.addr == NULL) {
736           appData.monoMode = True;
737           return True;
738         } else {
739           *color = *(Pixel *) vTo.addr;
740         }
741     }
742     return False;
743 }
744
745 int
746 MakeColors ()
747 {   // [HGM] taken out of main(), so it can be called from BoardOptions dialog
748     int forceMono = False;
749
750     if (appData.lowTimeWarning)
751         forceMono |= MakeOneColor(appData.lowTimeWarningColor, &lowTimeWarningColor);
752     if(appData.dialogColor[0]) MakeOneColor(appData.dialogColor, &dialogColor);
753     if(appData.buttonColor[0]) MakeOneColor(appData.buttonColor, &buttonColor);
754
755     return forceMono;
756 }
757
758 void
759 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
760 {   // detervtomine what fonts to use, and create them
761     XrmValue vTo;
762     XrmDatabase xdb;
763
764     if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
765         appData.clockFont = fontTable[CLOCK_FONT][squareSize];
766     if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
767         appData.font = fontTable[MESSAGE_FONT][squareSize];
768     if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
769         appData.coordFont = fontTable[COORD_FONT][squareSize];
770
771 #if ENABLE_NLS
772     appData.font = InsertPxlSize(appData.font, fontPxlSize);
773     appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
774     appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
775     fontSet = CreateFontSet(appData.font);
776     clockFontSet = CreateFontSet(appData.clockFont);
777     {
778       /* For the coordFont, use the 0th font of the fontset. */
779       XFontSet coordFontSet = CreateFontSet(appData.coordFont);
780       XFontStruct **font_struct_list;
781       XFontSetExtents *fontSize;
782       char **font_name_list;
783       XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
784       coordFontID = XLoadFont(xDisplay, font_name_list[0]);
785       coordFontStruct = XQueryFont(xDisplay, coordFontID);
786       fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
787       textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
788     }
789 #else
790     appData.font = FindFont(appData.font, fontPxlSize);
791     appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
792     appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
793     clockFontID = XLoadFont(xDisplay, appData.clockFont);
794     clockFontStruct = XQueryFont(xDisplay, clockFontID);
795     coordFontID = XLoadFont(xDisplay, appData.coordFont);
796     coordFontStruct = XQueryFont(xDisplay, coordFontID);
797     // textHeight in !NLS mode!
798 #endif
799     countFontID = coordFontID;  // [HGM] holdings
800     countFontStruct = coordFontStruct;
801
802     xdb = XtDatabase(xDisplay);
803 #if ENABLE_NLS
804     XrmPutLineResource(&xdb, "*international: True");
805     vTo.size = sizeof(XFontSet);
806     vTo.addr = (XtPointer) &fontSet;
807     XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
808 #else
809     XrmPutStringResource(&xdb, "*font", appData.font);
810 #endif
811 }
812
813 char *
814 PrintArg (ArgType t)
815 {
816   char *p="";
817   switch(t) {
818     case ArgZ:
819     case ArgInt:      p = " N"; break;
820     case ArgString:   p = " STR"; break;
821     case ArgBoolean:  p = " TF"; break;
822     case ArgSettingsFilename:
823     case ArgBackupSettingsFile:
824     case ArgFilename: p = " FILE"; break;
825     case ArgX:        p = " Nx"; break;
826     case ArgY:        p = " Ny"; break;
827     case ArgAttribs:  p = " TEXTCOL"; break;
828     case ArgColor:    p = " COL"; break;
829     case ArgFont:     p = " FONT"; break;
830     case ArgBoardSize: p = " SIZE"; break;
831     case ArgFloat: p = " FLOAT"; break;
832     case ArgTrue:
833     case ArgFalse:
834     case ArgTwo:
835     case ArgNone:
836     case ArgCommSettings:
837     case ArgMaster:
838     case ArgInstall:
839       break;
840   }
841   return p;
842 }
843
844 char *
845 GenerateGlobalTranslationTable (void)
846 {
847   /* go through all menu items and extract the keyboard shortcuts, so that X11 can load them */
848   char *output[2];
849
850   int i,j,n=0;
851   MenuItem *mi;
852
853   output[0] = strdup(""); // build keystrokes with and wo mod keys separately
854   output[1] = strdup(""); // so the more specific can preceed the other
855
856   /* loop over all menu entries */
857   for( i=0; menuBar[i-n].mi || !n++; i++)
858     {
859       mi = menuBar[i+n].mi; // kludge to access 'noMenu' behind sentinel
860       for(j=0; mi[j].proc; j++)
861         {
862           if (mi[j].accel)
863             {
864               int ctrl  = 0;
865               int shift = 0;
866               int alt   = 0;
867
868               char *key,*test, *mods;
869
870               /* check for Ctrl/Alt */
871               if( strstr(mi[j].accel, "<Ctrl>")  ) ctrl  = 1;
872               if( strstr(mi[j].accel, "<Shift>") ) shift = 1;
873               if( strstr(mi[j].accel, "<Alt>")   ) alt   = 1;
874
875               /* remove all <...> */
876               test = strrchr(mi[j].accel, '>');
877               if ( test==NULL )
878                 key = strdup(mi[j].accel);
879               else
880                 key = strdup(++test); // remove ">"
881
882               /* instead of shift X11 uses the uppercase letter directly*/
883               if (shift && strlen(key)==1 )
884                 {
885                   *key  = toupper(*key);
886                   shift = 0;
887                 }
888
889               /* handle some special cases which have different names in X11 */
890               if ( strncmp(key, "Page_Down", 9) == 0 )
891                 {
892                   free(key);
893                   key=strdup("Next");
894                 }
895               else if ( strncmp(key, "Page_Up", 7) == 0 )
896                 {
897                   free(key);
898                   key=strdup("Prior");
899                 };
900
901               /* create string of mods */
902               if (ctrl)
903                 mods = strdup("Ctrl ");
904               else
905                 mods = strdup("");
906
907               if(alt)
908                 {
909                   mods = realloc(mods, strlen(mods) + strlen("Meta ")+1);
910                   strncat(mods, "Meta ", 5);
911                 };
912
913               if(shift)
914                 {
915                   mods = realloc(mods, strlen(mods) + strlen("Shift ")+1);
916                   strncat(mods, "Shift ", 6);
917                 };
918
919               // remove trailing space
920               if( isspace(mods[strlen(mods)-1]) )
921                 mods[strlen(mods)-1]='\0';
922
923               /* get the name for the callback, we can use MenuItem() here that will call KeyBindingProc */
924               char *name = malloc(MSG_SIZ);
925               if(n) snprintf(name, MSG_SIZ, "%s", mi[j].ref);
926               else  snprintf(name, MSG_SIZ, "%s.%s", menuBar[i].ref, mi[j].ref);
927
928               char *buffer = malloc(MSG_SIZ);
929               snprintf(buffer, MSG_SIZ, ":%s<Key>%s: MenuItem(%s) \n ", mods, key, name);
930
931               /* add string to the output */
932               output[shift|alt|ctrl] = realloc(output[shift|alt|ctrl], strlen(output[shift|alt|ctrl]) + strlen(buffer)+1);
933               strncat(output[shift|alt|ctrl], buffer, strlen(buffer));
934
935               /* clean up */
936               free(key);
937               free(buffer);
938               free(name);
939               free(mods);
940             }
941         }
942     }
943   output[1] = realloc(output[1], strlen(output[1]) + strlen(output[0])+1);
944   strncat(output[1], output[0], strlen(output[0]));
945   free(output[0]);
946   return output[1];
947 }
948
949
950 void
951 PrintOptions ()
952 {
953   char buf[MSG_SIZ];
954   int len=0;
955   ArgDescriptor *q, *p = argDescriptors+5;
956   printf("\nXBoard accepts the following options:\n"
957          "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
958          " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
959          " SIZE = board-size spec(s)\n"
960          " Within parentheses are short forms, or options to set to true or false.\n"
961          " Persistent options (saved in the settings file) are marked with *)\n\n");
962   while(p->argName) {
963     if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
964     snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
965     if(p->save) strcat(buf+len, "*");
966     for(q=p+1; q->argLoc == p->argLoc; q++) {
967       if(q->argName[0] == '-') continue;
968       strcat(buf+len, q == p+1 ? " (" : " ");
969       sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
970     }
971     if(q != p+1) strcat(buf+len, ")");
972     len = strlen(buf);
973     if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
974     p = q;
975   }
976   if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
977 }
978
979 void
980 SlaveResize (Option *opt)
981 {
982 }
983
984 int
985 main (int argc, char **argv)
986 {
987     int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
988     XSetWindowAttributes window_attributes;
989     Arg args[16];
990     Dimension boardWidth, boardHeight, w, h;
991     char *p;
992     int forceMono = False;
993
994     extern Option chatOptions[]; // FIXME: adapt Chat window, removing ICS pane and Hide button
995     chatOptions[6].type = chatOptions[10].type = Skip;
996
997     srandom(time(0)); // [HGM] book: make random truly random
998
999     setbuf(stdout, NULL);
1000     setbuf(stderr, NULL);
1001     debugFP = stderr;
1002
1003     if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
1004       printf("%s version %s\n\n  configure options: %s\n", PACKAGE_NAME, PACKAGE_VERSION, CONFIGURE_OPTIONS);
1005         exit(0);
1006     }
1007
1008     if(argc > 1 && !strcmp(argv[1], "--help" )) {
1009         PrintOptions();
1010         exit(0);
1011     }
1012
1013     if(argc > 1 && !strcmp(argv[1], "--show-config")) { // [HGM] install: called to print config info
1014         typedef struct {char *name, *value; } Config;
1015         static Config configList[] = {
1016           { "Datadir", DATADIR },
1017           { "Sysconfdir", SYSCONFDIR },
1018           { NULL }
1019         };
1020         int i;
1021
1022         for(i=0; configList[i].name; i++) {
1023             if(argc > 2 && strcmp(argv[2], configList[i].name)) continue;
1024             if(argc > 2) printf("%s", configList[i].value);
1025             else printf("%-12s: %s\n", configList[i].name, configList[i].value);
1026         }
1027         exit(0);
1028     }
1029
1030     programName = strrchr(argv[0], '/');
1031     if (programName == NULL)
1032       programName = argv[0];
1033     else
1034       programName++;
1035
1036 #ifdef ENABLE_NLS
1037     XtSetLanguageProc(NULL, NULL, NULL);
1038     if (appData.debugMode) {
1039       fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
1040     }
1041
1042     bindtextdomain(PACKAGE, LOCALEDIR);
1043     textdomain(PACKAGE);
1044 #endif
1045
1046     appData.boardSize = "";
1047     InitAppData(ConvertToLine(argc, argv));
1048     p = getenv("HOME");
1049     if (p == NULL) p = "/tmp";
1050     i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
1051     gameCopyFilename = (char*) malloc(i);
1052     gamePasteFilename = (char*) malloc(i);
1053     snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
1054     snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
1055
1056     { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
1057         static char buf[MSG_SIZ];
1058         EscapeExpand(buf, appData.firstInitString);
1059         appData.firstInitString = strdup(buf);
1060         EscapeExpand(buf, appData.secondInitString);
1061         appData.secondInitString = strdup(buf);
1062         EscapeExpand(buf, appData.firstComputerString);
1063         appData.firstComputerString = strdup(buf);
1064         EscapeExpand(buf, appData.secondComputerString);
1065         appData.secondComputerString = strdup(buf);
1066     }
1067
1068     if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
1069         chessDir = ".";
1070     } else {
1071         if (chdir(chessDir) != 0) {
1072             fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
1073             perror(chessDir);
1074             exit(1);
1075         }
1076     }
1077
1078     if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
1079         /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
1080         if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL)  {
1081            printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
1082            exit(errno);
1083         }
1084         setbuf(debugFP, NULL);
1085     }
1086
1087     /* [HGM,HR] make sure board size is acceptable */
1088     if(appData.NrFiles > BOARD_FILES ||
1089        appData.NrRanks > BOARD_RANKS   )
1090          DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
1091
1092 #if !HIGHDRAG
1093     /* This feature does not work; animation needs a rewrite */
1094     appData.highlightDragging = FALSE;
1095 #endif
1096     InitBackEnd1();
1097
1098         gameInfo.variant = StringToVariant(appData.variant);
1099         InitPosition(FALSE);
1100
1101     shellWidget =
1102       XtAppInitialize(&appContext, "XBoard", shellOptions,
1103                       XtNumber(shellOptions),
1104                       &argc, argv, xboardResources, NULL, 0);
1105
1106     XtGetApplicationResources(shellWidget, (XtPointer) &appData,
1107                               clientResources, XtNumber(clientResources),
1108                               NULL, 0);
1109
1110     xDisplay = XtDisplay(shellWidget);
1111     xScreen = DefaultScreen(xDisplay);
1112     wm_delete_window = XInternAtom(xDisplay, "WM_DELETE_WINDOW", True);
1113
1114     /*
1115      * determine size, based on supplied or remembered -size, or screen size
1116      */
1117     if (isdigit(appData.boardSize[0])) {
1118         i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
1119                    &lineGap, &clockFontPxlSize, &coordFontPxlSize,
1120                    &fontPxlSize, &smallLayout, &tinyLayout);
1121         if (i == 0) {
1122             fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
1123                     programName, appData.boardSize);
1124             exit(2);
1125         }
1126         if(BOARD_WIDTH > 8)
1127             squareSize = (squareSize*8 + BOARD_WIDTH/2)/BOARD_WIDTH; // scale height
1128         if (i < 7) {
1129             /* Find some defaults; use the nearest known size */
1130             SizeDefaults *szd, *nearest;
1131             int distance = 99999;
1132             nearest = szd = sizeDefaults;
1133             while (szd->name != NULL) {
1134                 if (abs(szd->squareSize - squareSize) < distance) {
1135                     nearest = szd;
1136                     distance = abs(szd->squareSize - squareSize);
1137                     if (distance == 0) break;
1138                 }
1139                 szd++;
1140             }
1141             if (i < 2) lineGap = nearest->lineGap;
1142             if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
1143             if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
1144             if (i < 5) fontPxlSize = nearest->fontPxlSize;
1145             if (i < 6) smallLayout = nearest->smallLayout;
1146             if (i < 7) tinyLayout = nearest->tinyLayout;
1147         }
1148     } else {
1149         SizeDefaults *szd = sizeDefaults;
1150         if (*appData.boardSize == NULLCHAR) {
1151             while (DisplayWidth(xDisplay, xScreen)  < (szd->minScreenSize*BOARD_WIDTH  + 4)/8 ||
1152                    DisplayHeight(xDisplay, xScreen) < (szd->minScreenSize*BOARD_HEIGHT + 4)/8) {
1153               szd++;
1154             }
1155             if (szd->name == NULL) szd--;
1156             appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
1157         } else {
1158             while (szd->name != NULL &&
1159                    StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
1160             if (szd->name == NULL) {
1161                 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
1162                         programName, appData.boardSize);
1163                 exit(2);
1164             }
1165         }
1166         squareSize = szd->squareSize;
1167         lineGap = szd->lineGap;
1168         clockFontPxlSize = szd->clockFontPxlSize;
1169         coordFontPxlSize = szd->coordFontPxlSize;
1170         fontPxlSize = szd->fontPxlSize;
1171         smallLayout = szd->smallLayout;
1172         tinyLayout = szd->tinyLayout;
1173         // [HGM] font: use defaults from settings file if available and not overruled
1174     }
1175
1176     defaultLineGap = lineGap;
1177     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
1178
1179     /* [HR] height treated separately (hacked) */
1180     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
1181     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1182
1183     /*
1184      * Determine what fonts to use.
1185      */
1186     InitializeFonts(clockFontPxlSize, coordFontPxlSize, fontPxlSize);
1187
1188     /*
1189      * Detect if there are not enough colors available and adapt.
1190      */
1191     if (DefaultDepth(xDisplay, xScreen) <= 2) {
1192       appData.monoMode = True;
1193     }
1194
1195     forceMono = MakeColors();
1196
1197     if (forceMono) {
1198       fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1199               programName);
1200         appData.monoMode = True;
1201     }
1202
1203     if (appData.monoMode && appData.debugMode) {
1204         fprintf(stderr, _("white pixel = 0x%lx, black pixel = 0x%lx\n"),
1205                 (unsigned long) XWhitePixel(xDisplay, xScreen),
1206                 (unsigned long) XBlackPixel(xDisplay, xScreen));
1207     }
1208
1209     ParseIcsTextColors();
1210
1211     XtAppAddActions(appContext, boardActions, XtNumber(boardActions));
1212
1213     /*
1214      * widget hierarchy
1215      */
1216     if (tinyLayout) {
1217         layoutName = "tinyLayout";
1218     } else if (smallLayout) {
1219         layoutName = "smallLayout";
1220     } else {
1221         layoutName = "normalLayout";
1222     }
1223
1224     optList = BoardPopUp(squareSize, lineGap, (void*)
1225 #if ENABLE_NLS
1226                                                 &clockFontSet);
1227 #else
1228                                                 clockFontStruct);
1229 #endif
1230     InitDrawingHandle(optList + W_BOARD);
1231     currBoard        = &optList[W_BOARD];
1232     boardWidget      = optList[W_BOARD].handle;
1233     menuBarWidget    = optList[W_MENU].handle;
1234     dropMenu         = optList[W_DROP].handle;
1235     titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1236     formWidget  = XtParent(boardWidget);
1237     XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1238     XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1239     XtGetValues(optList[W_WHITE].handle, args, 2);
1240     if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1241       XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1242       XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1243       XtGetValues(optList[W_PAUSE].handle, args, 2);
1244     }
1245
1246     xBoardWindow = XtWindow(boardWidget);
1247
1248     // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1249     //       not need to go into InitDrawingSizes().
1250
1251     /*
1252      * Create X checkmark bitmap and initialize option menu checks.
1253      */
1254     ReadBitmap(&xMarkPixmap, "checkmark.bm",
1255                checkmark_bits, checkmark_width, checkmark_height);
1256     InitMenuMarkers();
1257
1258     /*
1259      * Create an icon.
1260      */
1261     ReadBitmap(&wIconPixmap, "icon_white.bm",
1262                icon_white_bits, icon_white_width, icon_white_height);
1263     ReadBitmap(&bIconPixmap, "icon_black.bm",
1264                icon_black_bits, icon_black_width, icon_black_height);
1265     iconPixmap = wIconPixmap;
1266     i = 0;
1267     XtSetArg(args[i], XtNiconPixmap, iconPixmap);  i++;
1268     XtSetValues(shellWidget, args, i);
1269
1270     /*
1271      * Create a cursor for the board widget.
1272      */
1273     window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1274     XChangeWindowAttributes(xDisplay, xBoardWindow,
1275                             CWCursor, &window_attributes);
1276
1277     /*
1278      * Inhibit shell resizing.
1279      */
1280     shellArgs[0].value = (XtArgVal) &w;
1281     shellArgs[1].value = (XtArgVal) &h;
1282     XtGetValues(shellWidget, shellArgs, 2);
1283     shellArgs[4].value = shellArgs[2].value = w;
1284     shellArgs[5].value = shellArgs[3].value = h;
1285 //    XtSetValues(shellWidget, &shellArgs[2], 4);
1286     marginW =  w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1287     marginH =  h - boardHeight;
1288
1289     CatchDeleteWindow(shellWidget, "QuitProc");
1290
1291     CreateAnyPieces(1);
1292     CreateGrid();
1293
1294     if(appData.logoSize)
1295     {   // locate and read user logo
1296         char buf[MSG_SIZ], name[MSG_SIZ];
1297         snprintf(name, MSG_SIZ, "/home/%s", UserName());
1298         if(!FindLogo(name, ".logo", buf))
1299             FindLogo(appData.logoDir, name + 6, buf);
1300         ASSIGN(userLogo, buf);
1301     }
1302
1303     if (appData.animate || appData.animateDragging)
1304       CreateAnimVars();
1305
1306
1307     char *TranslationsTableMenus=GenerateGlobalTranslationTable ();
1308
1309     XtAugmentTranslations(formWidget,
1310                           XtParseTranslationTable(globalTranslations));
1311     XtAugmentTranslations(formWidget,
1312                           XtParseTranslationTable(TranslationsTableMenus));
1313
1314     XtAddEventHandler(formWidget, KeyPressMask, False,
1315                       (XtEventHandler) MoveTypeInProc, NULL);
1316     XtAddEventHandler(shellWidget, StructureNotifyMask, False,
1317                       (XtEventHandler) EventProc, NULL);
1318
1319     /* [AS] Restore layout */
1320     if( wpMoveHistory.visible ) {
1321       HistoryPopUp();
1322     }
1323
1324     if( wpEvalGraph.visible )
1325       {
1326         EvalGraphPopUp();
1327       };
1328
1329     if( wpEngineOutput.visible ) {
1330       EngineOutputPopUp();
1331     }
1332
1333     gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1334     InitPosition(TRUE);
1335
1336     InitBackEnd2();
1337
1338     if (errorExitStatus == -1) {
1339         if (appData.icsActive) {
1340             /* We now wait until we see "login:" from the ICS before
1341                sending the logon script (problems with timestamp otherwise) */
1342             /*ICSInitScript();*/
1343             if (appData.icsInputBox) ICSInputBoxPopUp();
1344         }
1345
1346     #ifdef SIGWINCH
1347     signal(SIGWINCH, TermSizeSigHandler);
1348     #endif
1349         signal(SIGINT, IntSigHandler);
1350         signal(SIGTERM, IntSigHandler);
1351         if (*appData.cmailGameName != NULLCHAR) {
1352             signal(SIGUSR1, CmailSigHandler);
1353         }
1354     }
1355
1356     UpdateLogos(TRUE);
1357 //    XtSetKeyboardFocus(shellWidget, formWidget);
1358     XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1359
1360     XtAppMainLoop(appContext);
1361     if (appData.debugMode) fclose(debugFP); // [DM] debug
1362     return 0;
1363 }
1364
1365 void
1366 DoEvents ()
1367 {
1368     XtInputMask m;
1369     while((m = XtAppPending(appContext))) XtAppProcessEvent(appContext, m);
1370 }
1371
1372 RETSIGTYPE
1373 TermSizeSigHandler (int sig)
1374 {
1375     update_ics_width();
1376 }
1377
1378 RETSIGTYPE
1379 IntSigHandler (int sig)
1380 {
1381     ExitEvent(sig);
1382 }
1383
1384 RETSIGTYPE
1385 CmailSigHandler (int sig)
1386 {
1387     int dummy = 0;
1388     int error;
1389
1390     signal(SIGUSR1, SIG_IGN);   /* suspend handler     */
1391
1392     /* Activate call-back function CmailSigHandlerCallBack()             */
1393     OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1394
1395     signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1396 }
1397
1398 void
1399 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1400 {
1401     BoardToTop();
1402     ReloadCmailMsgEvent(TRUE);  /* Reload cmail msg  */
1403 }
1404 /**** end signal code ****/
1405
1406
1407 #define Abs(n) ((n)<0 ? -(n) : (n))
1408
1409 #ifdef ENABLE_NLS
1410 char *
1411 InsertPxlSize (char *pattern, int targetPxlSize)
1412 {
1413     char *base_fnt_lst, strInt[12], *p, *q;
1414     int alternatives, i, len, strIntLen;
1415
1416     /*
1417      * Replace the "*" (if present) in the pixel-size slot of each
1418      * alternative with the targetPxlSize.
1419      */
1420     p = pattern;
1421     alternatives = 1;
1422     while ((p = strchr(p, ',')) != NULL) {
1423       alternatives++;
1424       p++;
1425     }
1426     snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1427     strIntLen = strlen(strInt);
1428     base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1429
1430     p = pattern;
1431     q = base_fnt_lst;
1432     while (alternatives--) {
1433       char *comma = strchr(p, ',');
1434       for (i=0; i<14; i++) {
1435         char *hyphen = strchr(p, '-');
1436         if (!hyphen) break;
1437         if (comma && hyphen > comma) break;
1438         len = hyphen + 1 - p;
1439         if (i == 7 && *p == '*' && len == 2) {
1440           p += len;
1441           memcpy(q, strInt, strIntLen);
1442           q += strIntLen;
1443           *q++ = '-';
1444         } else {
1445           memcpy(q, p, len);
1446           p += len;
1447           q += len;
1448         }
1449       }
1450       if (!comma) break;
1451       len = comma + 1 - p;
1452       memcpy(q, p, len);
1453       p += len;
1454       q += len;
1455     }
1456     strcpy(q, p);
1457
1458     return base_fnt_lst;
1459 }
1460
1461 XFontSet
1462 CreateFontSet (char *base_fnt_lst)
1463 {
1464     XFontSet fntSet;
1465     char **missing_list;
1466     int missing_count;
1467     char *def_string;
1468
1469     fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1470                             &missing_list, &missing_count, &def_string);
1471     if (appData.debugMode) {
1472       int i, count;
1473       XFontStruct **font_struct_list;
1474       char **font_name_list;
1475       fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1476       if (fntSet) {
1477         fprintf(debugFP, " got list %s, locale %s\n",
1478                 XBaseFontNameListOfFontSet(fntSet),
1479                 XLocaleOfFontSet(fntSet));
1480         count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1481         for (i = 0; i < count; i++) {
1482           fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1483         }
1484       }
1485       for (i = 0; i < missing_count; i++) {
1486         fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1487       }
1488     }
1489     if (fntSet == NULL) {
1490       fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1491       exit(2);
1492     }
1493     return fntSet;
1494 }
1495 #else // not ENABLE_NLS
1496 /*
1497  * Find a font that matches "pattern" that is as close as
1498  * possible to the targetPxlSize.  Prefer fonts that are k
1499  * pixels smaller to fonts that are k pixels larger.  The
1500  * pattern must be in the X Consortium standard format,
1501  * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1502  * The return value should be freed with XtFree when no
1503  * longer needed.
1504  */
1505 char *
1506 FindFont (char *pattern, int targetPxlSize)
1507 {
1508     char **fonts, *p, *best, *scalable, *scalableTail;
1509     int i, j, nfonts, minerr, err, pxlSize;
1510
1511     fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1512     if (nfonts < 1) {
1513         fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1514                 programName, pattern);
1515         exit(2);
1516     }
1517
1518     best = fonts[0];
1519     scalable = NULL;
1520     minerr = 999999;
1521     for (i=0; i<nfonts; i++) {
1522         j = 0;
1523         p = fonts[i];
1524         if (*p != '-') continue;
1525         while (j < 7) {
1526             if (*p == NULLCHAR) break;
1527             if (*p++ == '-') j++;
1528         }
1529         if (j < 7) continue;
1530         pxlSize = atoi(p);
1531         if (pxlSize == 0) {
1532             scalable = fonts[i];
1533             scalableTail = p;
1534         } else {
1535             err = pxlSize - targetPxlSize;
1536             if (Abs(err) < Abs(minerr) ||
1537                 (minerr > 0 && err < 0 && -err == minerr)) {
1538                 best = fonts[i];
1539                 minerr = err;
1540             }
1541         }
1542     }
1543     if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1544         /* If the error is too big and there is a scalable font,
1545            use the scalable font. */
1546         int headlen = scalableTail - scalable;
1547         p = (char *) XtMalloc(strlen(scalable) + 10);
1548         while (isdigit(*scalableTail)) scalableTail++;
1549         sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1550     } else {
1551         p = (char *) XtMalloc(strlen(best) + 2);
1552         safeStrCpy(p, best, strlen(best)+1 );
1553     }
1554     if (appData.debugMode) {
1555         fprintf(debugFP, "resolved %s at pixel size %d\n  to %s\n",
1556                 pattern, targetPxlSize, p);
1557     }
1558     XFreeFontNames(fonts);
1559     return p;
1560 }
1561 #endif
1562
1563 void
1564 ReadBitmap (Pixmap *pm, String name, unsigned char bits[], u_int wreq, u_int hreq)
1565 {
1566     if (bits != NULL) {
1567         *pm = XCreateBitmapFromData(xDisplay, xBoardWindow, (char *) bits,
1568                                     wreq, hreq);
1569     }
1570 }
1571
1572 void
1573 MarkMenuItem (char *menuRef, int state)
1574 {
1575     MenuItem *item = MenuNameToItem(menuRef);
1576
1577     if(item) {
1578         Arg args[2];
1579         XtSetArg(args[0], XtNleftBitmap, state ? xMarkPixmap : None);
1580         XtSetValues(item->handle, args, 1);
1581     }
1582 }
1583
1584 void
1585 EnableNamedMenuItem (char *menuRef, int state)
1586 {
1587     MenuItem *item = MenuNameToItem(menuRef);
1588
1589     if(item) XtSetSensitive(item->handle, state);
1590 }
1591
1592 void
1593 EnableButtonBar (int state)
1594 {
1595     XtSetSensitive(optList[W_BUTTON].handle, state);
1596 }
1597
1598
1599 void
1600 SetMenuEnables (Enables *enab)
1601 {
1602   while (enab->name != NULL) {
1603     EnableNamedMenuItem(enab->name, enab->value);
1604     enab++;
1605   }
1606 }
1607
1608 void
1609 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1610 {   // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1611     MenuItem *item;
1612     if(*nprms == 0) return;
1613     item = MenuNameToItem(prms[0]);
1614     if(item) ((MenuProc *) item->proc) ();
1615 }
1616
1617 void
1618 SetupDropMenu ()
1619 {
1620     int i, j, count;
1621     char label[32];
1622     Arg args[16];
1623     Widget entry;
1624     char* p;
1625
1626     for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1627         entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1628         p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1629                    dmEnables[i].piece);
1630         XtSetSensitive(entry, p != NULL || !appData.testLegality
1631                        /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1632                                        && !appData.icsActive));
1633         count = 0;
1634         while (p && *p++ == dmEnables[i].piece) count++;
1635         snprintf(label, sizeof(label), "%s  %d", dmEnables[i].widget, count);
1636         j = 0;
1637         XtSetArg(args[j], XtNlabel, label); j++;
1638         XtSetValues(entry, args, j);
1639     }
1640 }
1641
1642 static void
1643 do_flash_delay (unsigned long msec)
1644 {
1645     TimeDelay(msec);
1646 }
1647
1648 void
1649 FlashDelay (int flash_delay)
1650 {
1651         XSync(xDisplay, False);
1652         if(flash_delay) do_flash_delay(flash_delay);
1653 }
1654
1655 double
1656 Fraction (int x, int start, int stop)
1657 {
1658    double f = ((double) x - start)/(stop - start);
1659    if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1660    return f;
1661 }
1662
1663 static WindowPlacement wpNew;
1664
1665 void
1666 CoDrag (Widget sh, WindowPlacement *wp)
1667 {
1668     Arg args[16];
1669     int j=0, touch=0, fudge = 2;
1670     GetActualPlacement(sh, wp);
1671     if(abs(wpMain.x + wpMain.width + 2*frameX - wp->x)         < fudge) touch = 1; else // right touch
1672     if(abs(wp->x + wp->width + 2*frameX - wpMain.x)            < fudge) touch = 2; else // left touch
1673     if(abs(wpMain.y + wpMain.height + frameX + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1674     if(abs(wp->y + wp->height + frameX + frameY - wpMain.y)    < fudge) touch = 4;      // top touch
1675     if(!touch ) return; // only windows that touch co-move
1676     if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1677         int heightInc = wpNew.height - wpMain.height;
1678         double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1679         double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1680         wp->y += fracTop * heightInc;
1681         heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1682         if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1683     } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1684         int widthInc = wpNew.width - wpMain.width;
1685         double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1686         double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1687         wp->y += fracLeft * widthInc;
1688         widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1689         if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1690     }
1691     wp->x += wpNew.x - wpMain.x;
1692     wp->y += wpNew.y - wpMain.y;
1693     if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1694     if(touch == 3) wp->y += wpNew.height - wpMain.height;
1695     XtSetArg(args[j], XtNx, wp->x); j++;
1696     XtSetArg(args[j], XtNy, wp->y); j++;
1697     XtSetValues(sh, args, j);
1698 }
1699
1700 void
1701 ReSize (WindowPlacement *wp)
1702 {
1703         int sqx, sqy, w, h;
1704         if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1705         sqx = (wp->width  - lineGap - marginW) / BOARD_WIDTH - lineGap;
1706         sqy = (wp->height - lineGap - marginH) / BOARD_HEIGHT - lineGap;
1707         if(sqy < sqx) sqx = sqy;
1708         if(sqx != squareSize) {
1709             squareSize = sqx; // adopt new square size
1710             CreatePNGPieces(); // make newly scaled pieces
1711             InitDrawingSizes(0, 0); // creates grid etc.
1712         } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1713         w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1714         h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1715         if(optList[W_BOARD].max   > w) optList[W_BOARD].max = w;
1716         if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1717 }
1718
1719 static XtIntervalId delayedDragID = 0;
1720
1721 void
1722 DragProc ()
1723 {
1724         static int busy;
1725         if(busy) return;
1726
1727         busy = 1;
1728         GetActualPlacement(shellWidget, &wpNew);
1729         if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1730            wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1731             busy = 0; return; // false alarm
1732         }
1733         ReSize(&wpNew);
1734         if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1735         if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1736         if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1737         if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1738         wpMain = wpNew;
1739         DrawPosition(True, NULL);
1740         delayedDragID = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1741         busy = 0;
1742 }
1743
1744
1745 void
1746 DelayedDrag ()
1747 {
1748     if(delayedDragID) XtRemoveTimeOut(delayedDragID); // cancel pending
1749     delayedDragID =
1750       XtAppAddTimeOut(appContext, 200, (XtTimerCallbackProc) DragProc, (XtPointer) 0); // and schedule new one 50 msec later
1751 }
1752
1753 void
1754 EventProc (Widget widget, caddr_t unused, XEvent *event)
1755 {
1756     if(XtIsRealized(widget) && event->type == ConfigureNotify || appData.useStickyWindows)
1757         DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1758 }
1759
1760 /*
1761  * event handler for redrawing the board
1762  */
1763 void
1764 DrawPositionProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1765 {
1766     DrawPosition(True, NULL);
1767 }
1768
1769
1770 void
1771 HandlePV (Widget w, XEvent * event, String * params, Cardinal * nParams)
1772 {   // [HGM] pv: walk PV
1773     MovePV(event->xmotion.x, event->xmotion.y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
1774 }
1775
1776 extern int savedIndex;  /* gross that this is global */
1777
1778 void
1779 CommentClick (Widget w, XEvent * event, String * params, Cardinal * nParams)
1780 {
1781         String val;
1782         XawTextPosition index, dummy;
1783         Arg arg;
1784
1785         XawTextGetSelectionPos(w, &index, &dummy);
1786         XtSetArg(arg, XtNstring, &val);
1787         XtGetValues(w, &arg, 1);
1788         ReplaceComment(savedIndex, val);
1789         if(savedIndex != currentMove) ToNrEvent(savedIndex);
1790         LoadVariation( index, val ); // [HGM] also does the actual moving to it, now
1791 }
1792
1793
1794 /* Disable all user input other than deleting the window */
1795 static int frozen = 0;
1796
1797 void
1798 FreezeUI ()
1799 {
1800   if (frozen) return;
1801   /* Grab by a widget that doesn't accept input */
1802   XtAddGrab(optList[W_MESSG].handle, TRUE, FALSE);
1803   frozen = 1;
1804 }
1805
1806 /* Undo a FreezeUI */
1807 void
1808 ThawUI ()
1809 {
1810   if (!frozen) return;
1811   XtRemoveGrab(optList[W_MESSG].handle);
1812   frozen = 0;
1813 }
1814
1815 void
1816 ModeHighlight ()
1817 {
1818     Arg args[16];
1819     static int oldPausing = FALSE;
1820     static GameMode oldmode = (GameMode) -1;
1821     char *wname;
1822
1823     if (!boardWidget || !XtIsRealized(boardWidget)) return;
1824
1825     if (pausing != oldPausing) {
1826         oldPausing = pausing;
1827         MarkMenuItem("Mode.Pause", pausing);
1828
1829         if (appData.showButtonBar) {
1830           /* Always toggle, don't set.  Previous code messes up when
1831              invoked while the button is pressed, as releasing it
1832              toggles the state again. */
1833           {
1834             Pixel oldbg, oldfg;
1835             XtSetArg(args[0], XtNbackground, &oldbg);
1836             XtSetArg(args[1], XtNforeground, &oldfg);
1837             XtGetValues(optList[W_PAUSE].handle,
1838                         args, 2);
1839             XtSetArg(args[0], XtNbackground, oldfg);
1840             XtSetArg(args[1], XtNforeground, oldbg);
1841           }
1842           XtSetValues(optList[W_PAUSE].handle, args, 2);
1843         }
1844     }
1845
1846     wname = ModeToWidgetName(oldmode);
1847     if (wname != NULL) {
1848         MarkMenuItem(wname, False);
1849     }
1850     wname = ModeToWidgetName(gameMode);
1851     if (wname != NULL) {
1852         MarkMenuItem(wname, True);
1853     }
1854     oldmode = gameMode;
1855     MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1856
1857     /* Maybe all the enables should be handled here, not just this one */
1858     EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1859
1860     DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1861 }
1862
1863
1864 /*
1865  * Button/menu procedures
1866  */
1867
1868 /* this variable is shared between CopyPositionProc and SendPositionSelection */
1869 char *selected_fen_position=NULL;
1870
1871 Boolean
1872 SendPositionSelection (Widget w, Atom *selection, Atom *target,
1873                        Atom *type_return, XtPointer *value_return,
1874                        unsigned long *length_return, int *format_return)
1875 {
1876   char *selection_tmp;
1877
1878 //  if (!selected_fen_position) return False; /* should never happen */
1879   if (*target == XA_STRING || *target == XA_UTF8_STRING(xDisplay)){
1880    if (!selected_fen_position) { // since it never happens, we use it for indicating a game is being sent
1881     FILE* f = fopen(gameCopyFilename, "r"); // This code, taken from SendGameSelection, now merges the two
1882     long len;
1883     size_t count;
1884     if (f == NULL) return False;
1885     fseek(f, 0, 2);
1886     len = ftell(f);
1887     rewind(f);
1888     selection_tmp = XtMalloc(len + 1);
1889     count = fread(selection_tmp, 1, len, f);
1890     fclose(f);
1891     if (len != count) {
1892       XtFree(selection_tmp);
1893       return False;
1894     }
1895     selection_tmp[len] = NULLCHAR;
1896    } else {
1897     /* note: since no XtSelectionDoneProc was registered, Xt will
1898      * automatically call XtFree on the value returned.  So have to
1899      * make a copy of it allocated with XtMalloc */
1900     selection_tmp= XtMalloc(strlen(selected_fen_position)+16);
1901     safeStrCpy(selection_tmp, selected_fen_position, strlen(selected_fen_position)+16 );
1902    }
1903
1904     *value_return=selection_tmp;
1905     *length_return=strlen(selection_tmp);
1906     *type_return=*target;
1907     *format_return = 8; /* bits per byte */
1908     return True;
1909   } else if (*target == XA_TARGETS(xDisplay)) {
1910     Atom *targets_tmp = (Atom *) XtMalloc(2 * sizeof(Atom));
1911     targets_tmp[0] = XA_UTF8_STRING(xDisplay);
1912     targets_tmp[1] = XA_STRING;
1913     *value_return = targets_tmp;
1914     *type_return = XA_ATOM;
1915     *length_return = 2;
1916 #if 0
1917     // This code leads to a read of value_return out of bounds on 64-bit systems.
1918     // Other code which I have seen always sets *format_return to 32 independent of
1919     // sizeof(Atom) without adjusting *length_return. For instance see TextConvertSelection()
1920     // at http://cgit.freedesktop.org/xorg/lib/libXaw/tree/src/Text.c -- BJ
1921     *format_return = 8 * sizeof(Atom);
1922     if (*format_return > 32) {
1923       *length_return *= *format_return / 32;
1924       *format_return = 32;
1925     }
1926 #else
1927     *format_return = 32;
1928 #endif
1929     return True;
1930   } else {
1931     return False;
1932   }
1933 }
1934
1935 /* note: when called from menu all parameters are NULL, so no clue what the
1936  * Widget which was clicked on was, or what the click event was
1937  */
1938 void
1939 CopySomething (char *src)
1940 {
1941     selected_fen_position = src;
1942     /*
1943      * Set both PRIMARY (the selection) and CLIPBOARD, since we don't
1944      * have a notion of a position that is selected but not copied.
1945      * See http://www.freedesktop.org/wiki/Specifications/ClipboardsWiki
1946      */
1947     XtOwnSelection(menuBarWidget, XA_PRIMARY,
1948                    CurrentTime,
1949                    SendPositionSelection,
1950                    NULL/* lose_ownership_proc */ ,
1951                    NULL/* transfer_done_proc */);
1952     XtOwnSelection(menuBarWidget, XA_CLIPBOARD(xDisplay),
1953                    CurrentTime,
1954                    SendPositionSelection,
1955                    NULL/* lose_ownership_proc */ ,
1956                    NULL/* transfer_done_proc */);
1957 }
1958
1959 /* function called when the data to Paste is ready */
1960 static void
1961 PastePositionCB (Widget w, XtPointer client_data, Atom *selection,
1962                  Atom *type, XtPointer value, unsigned long *len, int *format)
1963 {
1964   char *fenstr=value;
1965   if (value==NULL || *len==0) return; /* nothing had been selected to copy */
1966   fenstr[*len]='\0'; /* normally this string is terminated, but be safe */
1967   EditPositionPasteFEN(fenstr);
1968   XtFree(value);
1969 }
1970
1971 /* called when Paste Position button is pressed,
1972  * all parameters will be NULL */
1973 void
1974 PastePositionProc ()
1975 {
1976     XtGetSelectionValue(menuBarWidget,
1977       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
1978       /* (XtSelectionCallbackProc) */ PastePositionCB,
1979       NULL, /* client_data passed to PastePositionCB */
1980
1981       /* better to use the time field from the event that triggered the
1982        * call to this function, but that isn't trivial to get
1983        */
1984       CurrentTime
1985     );
1986     return;
1987 }
1988
1989 /* note: when called from menu all parameters are NULL, so no clue what the
1990  * Widget which was clicked on was, or what the click event was
1991  */
1992 /* function called when the data to Paste is ready */
1993 static void
1994 PasteGameCB (Widget w, XtPointer client_data, Atom *selection,
1995              Atom *type, XtPointer value, unsigned long *len, int *format)
1996 {
1997   FILE* f;
1998   if (value == NULL || *len == 0) {
1999     return; /* nothing had been selected to copy */
2000   }
2001   f = fopen(gamePasteFilename, "w");
2002   if (f == NULL) {
2003     DisplayError(_("Can't open temp file"), errno);
2004     return;
2005   }
2006   fwrite(value, 1, *len, f);
2007   fclose(f);
2008   XtFree(value);
2009   LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
2010 }
2011
2012 /* called when Paste Game button is pressed,
2013  * all parameters will be NULL */
2014 void
2015 PasteGameProc ()
2016 {
2017     XtGetSelectionValue(menuBarWidget,
2018       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
2019       /* (XtSelectionCallbackProc) */ PasteGameCB,
2020       NULL, /* client_data passed to PasteGameCB */
2021
2022       /* better to use the time field from the event that triggered the
2023        * call to this function, but that isn't trivial to get
2024        */
2025       CurrentTime
2026     );
2027     return;
2028 }
2029
2030
2031 void
2032 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2033 {
2034     QuitProc();
2035 }
2036
2037 int
2038 ShiftKeys ()
2039 {   // bassic primitive for determining if modifier keys are pressed
2040     long int codes[] = { XK_Meta_L, XK_Meta_R, XK_Control_L, XK_Control_R, XK_Shift_L, XK_Shift_R };
2041     char keys[32];
2042     int i,j,  k=0;
2043     XQueryKeymap(xDisplay,keys);
2044     for(i=0; i<6; i++) {
2045         k <<= 1;
2046         j = XKeysymToKeycode(xDisplay, codes[i]);
2047         k += ( (keys[j>>3]&1<<(j&7)) != 0 );
2048     }
2049     return k;
2050 }
2051
2052 static void
2053 MoveTypeInProc (Widget widget, caddr_t unused, XEvent *event)
2054 {
2055     char buf[10];
2056     KeySym sym;
2057     int n = XLookupString(&(event->xkey), buf, 10, &sym, NULL);
2058     if ( n == 1 && *buf >= 32 // printable
2059          && !(ShiftKeys() & 0x3C) // no Alt, Ctrl
2060         ) BoxAutoPopUp (buf);
2061 }
2062
2063 static void
2064 UpKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2065 {   // [HGM] input: let up-arrow recall previous line from history
2066     IcsKey(1);
2067 }
2068
2069 static void
2070 DownKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2071 {   // [HGM] input: let down-arrow recall next line from history
2072     IcsKey(-1);
2073 }
2074
2075 static void
2076 EnterKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2077 {
2078     IcsKey(0);
2079 }
2080
2081 void
2082 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2083 {
2084         if (!TempBackwardActive) {
2085                 TempBackwardActive = True;
2086                 BackwardEvent();
2087         }
2088 }
2089
2090 void
2091 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2092 {
2093         /* Check to see if triggered by a key release event for a repeating key.
2094          * If so the next queued event will be a key press of the same key at the same time */
2095         if (XEventsQueued(xDisplay, QueuedAfterReading)) {
2096                 XEvent next;
2097                 XPeekEvent(xDisplay, &next);
2098                 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
2099                         next.xkey.keycode == event->xkey.keycode)
2100                                 return;
2101         }
2102     ForwardEvent();
2103         TempBackwardActive = False;
2104 }
2105
2106 void
2107 ManInner (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2108 {   // called as key binding
2109     char buf[MSG_SIZ];
2110     String name;
2111     if (nprms && *nprms > 0)
2112       name = prms[0];
2113     else
2114       name = "xboard";
2115     snprintf(buf, sizeof(buf), "xterm -e man %s &", name);
2116     system(buf);
2117 }
2118
2119 void
2120 ManProc ()
2121 {   // called from menu
2122     ManInner(NULL, NULL, NULL, NULL);
2123 }
2124
2125 void
2126 SetWindowTitle (char *text, char *title, char *icon)
2127 {
2128     Arg args[16];
2129     int i;
2130     if (appData.titleInWindow) {
2131         i = 0;
2132         XtSetArg(args[i], XtNlabel, text);   i++;
2133         XtSetValues(titleWidget, args, i);
2134     }
2135     i = 0;
2136     XtSetArg(args[i], XtNiconName, (XtArgVal) icon);    i++;
2137     XtSetArg(args[i], XtNtitle, (XtArgVal) title);      i++;
2138     XtSetValues(shellWidget, args, i);
2139     XSync(xDisplay, False);
2140 }
2141
2142
2143 static int
2144 NullXErrorCheck (Display *dpy, XErrorEvent *error_event)
2145 {
2146     return 0;
2147 }
2148
2149 void
2150 DisplayIcsInteractionTitle (String message)
2151 {
2152   if (oldICSInteractionTitle == NULL) {
2153     /* Magic to find the old window title, adapted from vim */
2154     char *wina = getenv("WINDOWID");
2155     if (wina != NULL) {
2156       Window win = (Window) atoi(wina);
2157       Window root, parent, *children;
2158       unsigned int nchildren;
2159       int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
2160       for (;;) {
2161         if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
2162         if (!XQueryTree(xDisplay, win, &root, &parent,
2163                         &children, &nchildren)) break;
2164         if (children) XFree((void *)children);
2165         if (parent == root || parent == 0) break;
2166         win = parent;
2167       }
2168       XSetErrorHandler(oldHandler);
2169     }
2170     if (oldICSInteractionTitle == NULL) {
2171       oldICSInteractionTitle = "xterm";
2172     }
2173   }
2174   printf("\033]0;%s\007", message);
2175   fflush(stdout);
2176 }
2177
2178
2179 XtIntervalId delayedEventTimerXID = 0;
2180 DelayedEventCallback delayedEventCallback = 0;
2181
2182 void
2183 FireDelayedEvent ()
2184 {
2185     delayedEventTimerXID = 0;
2186     delayedEventCallback();
2187 }
2188
2189 void
2190 ScheduleDelayedEvent (DelayedEventCallback cb, long millisec)
2191 {
2192     if(delayedEventTimerXID && delayedEventCallback == cb)
2193         // [HGM] alive: replace, rather than add or flush identical event
2194         XtRemoveTimeOut(delayedEventTimerXID);
2195     delayedEventCallback = cb;
2196     delayedEventTimerXID =
2197       XtAppAddTimeOut(appContext, millisec,
2198                       (XtTimerCallbackProc) FireDelayedEvent, (XtPointer) 0);
2199 }
2200
2201 DelayedEventCallback
2202 GetDelayedEvent ()
2203 {
2204   if (delayedEventTimerXID) {
2205     return delayedEventCallback;
2206   } else {
2207     return NULL;
2208   }
2209 }
2210
2211 void
2212 CancelDelayedEvent ()
2213 {
2214   if (delayedEventTimerXID) {
2215     XtRemoveTimeOut(delayedEventTimerXID);
2216     delayedEventTimerXID = 0;
2217   }
2218 }
2219
2220 XtIntervalId loadGameTimerXID = 0;
2221
2222 int
2223 LoadGameTimerRunning ()
2224 {
2225     return loadGameTimerXID != 0;
2226 }
2227
2228 int
2229 StopLoadGameTimer ()
2230 {
2231     if (loadGameTimerXID != 0) {
2232         XtRemoveTimeOut(loadGameTimerXID);
2233         loadGameTimerXID = 0;
2234         return TRUE;
2235     } else {
2236         return FALSE;
2237     }
2238 }
2239
2240 void
2241 LoadGameTimerCallback (XtPointer arg, XtIntervalId *id)
2242 {
2243     loadGameTimerXID = 0;
2244     AutoPlayGameLoop();
2245 }
2246
2247 void
2248 StartLoadGameTimer (long millisec)
2249 {
2250     loadGameTimerXID =
2251       XtAppAddTimeOut(appContext, millisec,
2252                       (XtTimerCallbackProc) LoadGameTimerCallback,
2253                       (XtPointer) 0);
2254 }
2255
2256 XtIntervalId analysisClockXID = 0;
2257
2258 void
2259 AnalysisClockCallback (XtPointer arg, XtIntervalId *id)
2260 {
2261     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
2262          || appData.icsEngineAnalyze) { // [DM]
2263         AnalysisPeriodicEvent(0);
2264         StartAnalysisClock();
2265     }
2266 }
2267
2268 void
2269 StartAnalysisClock ()
2270 {
2271     analysisClockXID =
2272       XtAppAddTimeOut(appContext, 2000,
2273                       (XtTimerCallbackProc) AnalysisClockCallback,
2274                       (XtPointer) 0);
2275 }
2276
2277 XtIntervalId clockTimerXID = 0;
2278
2279 int
2280 ClockTimerRunning ()
2281 {
2282     return clockTimerXID != 0;
2283 }
2284
2285 int
2286 StopClockTimer ()
2287 {
2288     if (clockTimerXID != 0) {
2289         XtRemoveTimeOut(clockTimerXID);
2290         clockTimerXID = 0;
2291         return TRUE;
2292     } else {
2293         return FALSE;
2294     }
2295 }
2296
2297 void
2298 ClockTimerCallback (XtPointer arg, XtIntervalId *id)
2299 {
2300     clockTimerXID = 0;
2301     DecrementClocks();
2302 }
2303
2304 void
2305 StartClockTimer (long millisec)
2306 {
2307     clockTimerXID =
2308       XtAppAddTimeOut(appContext, millisec,
2309                       (XtTimerCallbackProc) ClockTimerCallback,
2310                       (XtPointer) 0);
2311 }
2312
2313 void
2314 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
2315 {
2316     char buf[MSG_SIZ];
2317     Arg args[16];
2318     Widget w = (Widget) opt->handle;
2319
2320     /* check for low time warning */
2321     Pixel foregroundOrWarningColor = timerForegroundPixel;
2322
2323     if (timer > 0 &&
2324         appData.lowTimeWarning &&
2325         (timer / 1000) < appData.icsAlarmTime)
2326       foregroundOrWarningColor = lowTimeWarningColor;
2327
2328     if (appData.clockMode) {
2329       snprintf(buf, MSG_SIZ, "%s:%s%s", color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2330       XtSetArg(args[0], XtNlabel, buf);
2331     } else {
2332       snprintf(buf, MSG_SIZ, "%s  ", color);
2333       XtSetArg(args[0], XtNlabel, buf);
2334     }
2335
2336     if (highlight) {
2337
2338         XtSetArg(args[1], XtNbackground, foregroundOrWarningColor);
2339         XtSetArg(args[2], XtNforeground, timerBackgroundPixel);
2340     } else {
2341         XtSetArg(args[1], XtNbackground, timerBackgroundPixel);
2342         XtSetArg(args[2], XtNforeground, foregroundOrWarningColor);
2343     }
2344
2345     XtSetValues(w, args, 3);
2346 }
2347
2348 static Pixmap *clockIcons[] = { &wIconPixmap, &bIconPixmap };
2349
2350 void
2351 SetClockIcon (int color)
2352 {
2353     Arg args[16];
2354     Pixmap pm = *clockIcons[color];
2355     if (iconPixmap != pm) {
2356         iconPixmap = pm;
2357         XtSetArg(args[0], XtNiconPixmap, iconPixmap);
2358         XtSetValues(shellWidget, args, 1);
2359     }
2360 }
2361
2362 #define INPUT_SOURCE_BUF_SIZE 8192
2363
2364 typedef struct {
2365     CPKind kind;
2366     int fd;
2367     int lineByLine;
2368     char *unused;
2369     InputCallback func;
2370     XtInputId xid;
2371     char buf[INPUT_SOURCE_BUF_SIZE];
2372     VOIDSTAR closure;
2373 } InputSource;
2374
2375 void
2376 DoInputCallback (caddr_t closure, int *source, XtInputId *xid)
2377 {
2378     InputSource *is = (InputSource *) closure;
2379     int count;
2380     int error;
2381     char *p, *q;
2382
2383     if (is->lineByLine) {
2384         count = read(is->fd, is->unused,
2385                      INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
2386         if (count <= 0) {
2387             (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
2388             return;
2389         }
2390         is->unused += count;
2391         p = is->buf;
2392         while (p < is->unused) {
2393             q = memchr(p, '\n', is->unused - p);
2394             if (q == NULL) break;
2395             q++;
2396             (is->func)(is, is->closure, p, q - p, 0);
2397             p = q;
2398         }
2399         q = is->buf;
2400         while (p < is->unused) {
2401             *q++ = *p++;
2402         }
2403         is->unused = q;
2404     } else {
2405         count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2406         if (count == -1)
2407           error = errno;
2408         else
2409           error = 0;
2410         (is->func)(is, is->closure, is->buf, count, error);
2411     }
2412 }
2413
2414 InputSourceRef
2415 AddInputSource (ProcRef pr, int lineByLine, InputCallback func, VOIDSTAR closure)
2416 {
2417     InputSource *is;
2418     ChildProc *cp = (ChildProc *) pr;
2419
2420     is = (InputSource *) calloc(1, sizeof(InputSource));
2421     is->lineByLine = lineByLine;
2422     is->func = func;
2423     if (pr == NoProc) {
2424         is->kind = CPReal;
2425         is->fd = fileno(stdin);
2426     } else {
2427         is->kind = cp->kind;
2428         is->fd = cp->fdFrom;
2429     }
2430     if (lineByLine) {
2431         is->unused = is->buf;
2432     }
2433
2434     is->xid = XtAppAddInput(appContext, is->fd,
2435                             (XtPointer) (XtInputReadMask),
2436                             (XtInputCallbackProc) DoInputCallback,
2437                             (XtPointer) is);
2438     is->closure = closure;
2439     return (InputSourceRef) is;
2440 }
2441
2442 void
2443 RemoveInputSource (InputSourceRef isr)
2444 {
2445     InputSource *is = (InputSource *) isr;
2446
2447     if (is->xid == 0) return;
2448     XtRemoveInput(is->xid);
2449     is->xid = 0;
2450 }
2451
2452 #ifndef HAVE_USLEEP
2453
2454 static Boolean frameWaiting;
2455
2456 static RETSIGTYPE
2457 FrameAlarm (int sig)
2458 {
2459   frameWaiting = False;
2460   /* In case System-V style signals.  Needed?? */
2461   signal(SIGALRM, FrameAlarm);
2462 }
2463
2464 void
2465 FrameDelay (int time)
2466 {
2467   struct itimerval delay;
2468
2469   XSync(xDisplay, False);
2470
2471   if (time > 0) {
2472     frameWaiting = True;
2473     signal(SIGALRM, FrameAlarm);
2474     delay.it_interval.tv_sec =
2475       delay.it_value.tv_sec = time / 1000;
2476     delay.it_interval.tv_usec =
2477       delay.it_value.tv_usec = (time % 1000) * 1000;
2478     setitimer(ITIMER_REAL, &delay, NULL);
2479     while (frameWaiting) pause();
2480     delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2481     delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2482     setitimer(ITIMER_REAL, &delay, NULL);
2483   }
2484 }
2485
2486 #else
2487
2488 void
2489 FrameDelay (int time)
2490 {
2491   XSync(xDisplay, False);
2492   if (time > 0)
2493     usleep(time * 1000);
2494 }
2495
2496 #endif
2497
2498 static int
2499 FindLogo (char *place, char *name, char *buf)
2500 {   // check if file exists in given place
2501     FILE *f;
2502     if(!place) return 0;
2503     snprintf(buf, MSG_SIZ, "%s/%s.png", place, name);
2504     if(*place && strcmp(place, ".") && (f = fopen(buf, "r")) ) {
2505         fclose(f);
2506         return 1;
2507     }
2508     return 0;
2509 }
2510
2511 static void
2512 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2513 {
2514     char buf[MSG_SIZ], *logoName = buf;
2515     if(appData.logo[n][0]) {
2516         logoName = appData.logo[n];
2517     } else if(appData.autoLogo) {
2518         if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2519             sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2520         } else { // engine; cascade
2521             if(!FindLogo(appData.logoDir, cps->tidy, buf) &&   // first try user log folder
2522                !FindLogo(appData.directory[n], "logo", buf) && // then engine directory
2523                !FindLogo("/usr/local/share/games/plugins/logos", cps->tidy, buf) ) // then system folders
2524                 FindLogo("/usr/share/games/plugins/logos", cps->tidy, buf);
2525         }
2526     }
2527     if(logoName[0])
2528         { ASSIGN(cps->programLogo, logoName); }
2529 }
2530
2531 void
2532 UpdateLogos (int displ)
2533 {
2534     if(optList[W_WHITE-1].handle == NULL) return;
2535     LoadLogo(&first, 0, 0);
2536     LoadLogo(&second, 1, appData.icsActive);
2537     if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2538     return;
2539 }
2540