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