99af0487cb335cda688e893f655b229fe6711d17
[xboard.git] / 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 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 #if HAVE_LIBXPM
187 #include <X11/xpm.h>
188 #include "pixmaps/pixmaps.h"
189 #define IMAGE_EXT "xpm"
190 #else
191 #define IMAGE_EXT "xim"
192 #include "bitmaps/bitmaps.h"
193 #endif
194
195 #include "bitmaps/icon_white.bm"
196 #include "bitmaps/icon_black.bm"
197 #include "bitmaps/checkmark.bm"
198
199 #include "frontend.h"
200 #include "backend.h"
201 #include "backendz.h"
202 #include "moves.h"
203 #include "xboard.h"
204 #include "childio.h"
205 #include "xgamelist.h"
206 #include "xhistory.h"
207 #include "xevalgraph.h"
208 #include "xedittags.h"
209 #include "menus.h"
210 #include "board.h"
211 #include "dialogs.h"
212 #include "engineoutput.h"
213 #include "usystem.h"
214 #include "gettext.h"
215
216
217 #ifdef __EMX__
218 #ifndef HAVE_USLEEP
219 #define HAVE_USLEEP
220 #endif
221 #define usleep(t)   _sleep2(((t)+500)/1000)
222 #endif
223
224 #ifdef ENABLE_NLS
225 # define  _(s) gettext (s)
226 # define N_(s) gettext_noop (s)
227 #else
228 # define  _(s) (s)
229 # define N_(s)  s
230 #endif
231
232 int main P((int argc, char **argv));
233 RETSIGTYPE CmailSigHandler P((int sig));
234 RETSIGTYPE IntSigHandler P((int sig));
235 RETSIGTYPE TermSizeSigHandler P((int sig));
236 static void CreateGCs P((int redo));
237 static void CreateAnyPieces P((void));
238 void CreateXIMPieces P((void));
239 void CreateXPMPieces P((void));
240 void CreateXPMBoard P((char *s, int n));
241 void CreatePieces P((void));
242 Widget CreateMenuBar P((Menu *mb, int boardWidth));
243 #if ENABLE_NLS
244 char *InsertPxlSize P((char *pattern, int targetPxlSize));
245 XFontSet CreateFontSet P((char *base_fnt_lst));
246 #else
247 char *FindFont P((char *pattern, int targetPxlSize));
248 #endif
249 void ReadBitmap P((Pixmap *pm, String name, unsigned char bits[],
250                    u_int wreq, u_int hreq));
251 void CreateGrid P((void));
252 void EventProc P((Widget widget, caddr_t unused, XEvent *event));
253 void DelayedDrag P((void));
254 static void MoveTypeInProc P((Widget widget, caddr_t unused, XEvent *event));
255 void HandlePV P((Widget w, XEvent * event,
256                      String * params, Cardinal * nParams));
257 void DrawPositionProc P((Widget w, XEvent *event,
258                      String *prms, Cardinal *nprms));
259 void CommentClick P((Widget w, XEvent * event,
260                    String * params, Cardinal * nParams));
261 void ICSInputBoxPopUp P((void));
262 void SelectCommand P((Widget w, XtPointer client_data, XtPointer call_data));
263 void KeyBindingProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
264 void QuitWrapper P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
265 static void EnterKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
266 static void UpKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
267 static void DownKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
268 void TempBackwardProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
269 void TempForwardProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
270 Boolean TempBackwardActive = False;
271 void ManInner P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
272 void DisplayMove P((int moveNumber));
273 void ICSInitScript P((void));
274 void SelectMove P((Widget w, XEvent * event, String * params, Cardinal * nParams));
275 void update_ics_width P(());
276 int CopyMemoProc P(());
277
278 /*
279 * XBoard depends on Xt R4 or higher
280 */
281 int xtVersion = XtSpecificationRelease;
282
283 int xScreen;
284 Display *xDisplay;
285 Window xBoardWindow;
286 Pixel lightSquareColor, darkSquareColor, whitePieceColor, blackPieceColor,
287   highlightSquareColor, premoveHighlightColor, dialogColor, buttonColor;
288 Pixel lowTimeWarningColor;
289 GC lightSquareGC, darkSquareGC, lineGC, wdPieceGC, wlPieceGC,
290   bdPieceGC, blPieceGC, wbPieceGC, bwPieceGC, coordGC, highlineGC,
291   prelineGC, countGC;
292 Pixmap iconPixmap, wIconPixmap, bIconPixmap, xMarkPixmap;
293 Widget shellWidget, formWidget, boardWidget, titleWidget, dropMenu, menuBarWidget;
294 Option *optList; // contains all widgets of main window
295 XSegment gridSegments[BOARD_RANKS + BOARD_FILES + 2];
296 #if ENABLE_NLS
297 XFontSet fontSet, clockFontSet;
298 #else
299 Font clockFontID;
300 XFontStruct *clockFontStruct;
301 #endif
302 Font coordFontID, countFontID;
303 XFontStruct *coordFontStruct, *countFontStruct;
304 XtAppContext appContext;
305 char *layoutName;
306
307 char installDir[] = "."; // [HGM] UCI: needed for UCI; probably needs run-time initializtion
308
309 Position commentX = -1, commentY = -1;
310 Dimension commentW, commentH;
311 typedef unsigned int BoardSize;
312 BoardSize boardSize;
313 Boolean chessProgram;
314
315 int  minX, minY; // [HGM] placement: volatile limits on upper-left corner
316 int smallLayout = 0, tinyLayout = 0,
317   marginW, marginH, // [HGM] for run-time resizing
318   fromX = -1, fromY = -1, toX, toY, commentUp = False,
319   errorExitStatus = -1, defaultLineGap;
320 Dimension textHeight;
321 Pixel timerForegroundPixel, timerBackgroundPixel;
322 Pixel buttonForegroundPixel, buttonBackgroundPixel;
323 char *chessDir, *programName, *programVersion;
324 Boolean alwaysOnTop = False;
325 char *icsTextMenuString;
326 char *icsNames;
327 char *firstChessProgramNames;
328 char *secondChessProgramNames;
329
330 WindowPlacement wpMain;
331 WindowPlacement wpConsole;
332 WindowPlacement wpComment;
333 WindowPlacement wpMoveHistory;
334 WindowPlacement wpEvalGraph;
335 WindowPlacement wpEngineOutput;
336 WindowPlacement wpGameList;
337 WindowPlacement wpTags;
338
339
340 #define SOLID 0
341 #define OUTLINE 1
342 Pixmap pieceBitmap[2][(int)BlackPawn];
343 Pixmap pieceBitmap2[2][(int)BlackPawn+4];       /* [HGM] pieces */
344 Pixmap xpmPieceBitmap[4][(int)BlackPawn];       /* LL, LD, DL, DD actually used*/
345 Pixmap xpmPieceBitmap2[4][(int)BlackPawn+4];    /* LL, LD, DL, DD set to select from */
346 Pixmap xpmLightSquare, xpmDarkSquare, xpmJailSquare;
347 Pixmap xpmBoardBitmap[2];
348 int useImages, useImageSqs, useTexture, textureW[2], textureH[2];
349 XImage *ximPieceBitmap[4][(int)BlackPawn+4];    /* LL, LD, DL, DD */
350 Pixmap ximMaskPm[(int)BlackPawn];               /* clipmasks, used for XIM pieces */
351 Pixmap ximMaskPm2[(int)BlackPawn+4];            /* clipmasks, used for XIM pieces */
352 XImage *ximLightSquare, *ximDarkSquare;
353 XImage *xim_Cross;
354
355 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
356 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
357
358 #define White(piece) ((int)(piece) < (int)BlackPawn)
359
360 /* Bitmaps for use as masks when drawing XPM pieces.
361    Need one for each black and white piece.             */
362 static Pixmap xpmMask[BlackKing + 1];
363
364 /* This magic number is the number of intermediate frames used
365    in each half of the animation. For short moves it's reduced
366    by 1. The total number of frames will be factor * 2 + 1.  */
367 #define kFactor    4
368
369 SizeDefaults sizeDefaults[] = SIZE_DEFAULTS;
370
371 typedef struct {
372     char piece;
373     char* widget;
374 } DropMenuEnables;
375
376 DropMenuEnables dmEnables[] = {
377     { 'P', "Pawn" },
378     { 'N', "Knight" },
379     { 'B', "Bishop" },
380     { 'R', "Rook" },
381     { 'Q', "Queen" }
382 };
383
384 Arg shellArgs[] = {
385     { XtNwidth, 0 },
386     { XtNheight, 0 },
387     { XtNminWidth, 0 },
388     { XtNminHeight, 0 },
389     { XtNmaxWidth, 0 },
390     { XtNmaxHeight, 0 }
391 };
392
393 XtResource clientResources[] = {
394     { "flashCount", "flashCount", XtRInt, sizeof(int),
395         XtOffset(AppDataPtr, flashCount), XtRImmediate,
396         (XtPointer) FLASH_COUNT  },
397 };
398
399 XrmOptionDescRec shellOptions[] = {
400     { "-flashCount", "flashCount", XrmoptionSepArg, NULL },
401     { "-flash", "flashCount", XrmoptionNoArg, "3" },
402     { "-xflash", "flashCount", XrmoptionNoArg, "0" },
403 };
404
405 XtActionsRec boardActions[] = {
406     { "DrawPosition", DrawPositionProc },
407     { "HandlePV", HandlePV },
408     { "SelectPV", SelectPV },
409     { "StopPV", StopPV },
410     { "MenuItem", KeyBindingProc }, // [HGM] generic handler for key bindings
411     { "QuitProc", QuitWrapper },
412     { "ManProc", ManInner },
413     { "TempBackwardProc", TempBackwardProc },
414     { "TempForwardProc", TempForwardProc },
415     { "CommentClick", (XtActionProc) CommentClick },
416     { "GenericPopDown", (XtActionProc) GenericPopDown },
417     { "ErrorPopDown", (XtActionProc) ErrorPopDown },
418     { "CopyMemoProc", (XtActionProc) CopyMemoProc },
419     { "SelectMove", (XtActionProc) SelectMove },
420     { "LoadSelectedProc", LoadSelectedProc },
421     { "SetFilterProc", SetFilterProc },
422     { "TypeInProc", TypeInProc },
423     { "EnterKeyProc", EnterKeyProc },
424     { "UpKeyProc", UpKeyProc },
425     { "DownKeyProc", DownKeyProc },
426     { "WheelProc", WheelProc },
427     { "TabProc", TabProc },
428 };
429
430 char globalTranslations[] =
431   ":<Key>F9: MenuItem(Actions.Resign) \n \
432    :Ctrl<Key>n: MenuItem(File.NewGame) \n \
433    :Meta<Key>V: MenuItem(File.NewVariant) \n \
434    :Ctrl<Key>o: MenuItem(File.LoadGame) \n \
435    :Meta<Key>Next: MenuItem(LoadNextGameProc) \n \
436    :Meta<Key>Prior: MenuItem(LoadPrevGameProc) \n \
437    :Ctrl<Key>Down: LoadSelectedProc(3) \n \
438    :Ctrl<Key>Up: LoadSelectedProc(-3) \n \
439    :Ctrl<Key>s: MenuItem(File.SaveGame) \n \
440    :Ctrl<Key>c: MenuItem(Edit.CopyGame) \n \
441    :Ctrl<Key>v: MenuItem(Edit.PasteGame) \n \
442    :Ctrl<Key>O: MenuItem(File.LoadPosition) \n \
443    :Shift<Key>Next: MenuItem(LoadNextPositionProc) \n \
444    :Shift<Key>Prior: MenuItem(LoadPrevPositionProc) \n \
445    :Ctrl<Key>S: MenuItem(File.SavePosition) \n \
446    :Ctrl<Key>C: MenuItem(Edit.CopyPosition) \n \
447    :Ctrl<Key>V: MenuItem(Edit.PastePosition) \n \
448    :Ctrl<Key>q: MenuItem(File.Quit) \n \
449    :Ctrl<Key>w: MenuItem(Mode.MachineWhite) \n \
450    :Ctrl<Key>b: MenuItem(Mode.MachineBlack) \n \
451    :Ctrl<Key>t: MenuItem(Mode.TwoMachines) \n \
452    :Ctrl<Key>a: MenuItem(Mode.AnalysisMode) \n \
453    :Ctrl<Key>g: MenuItem(Mode.AnalyzeFile) \n \
454    :Ctrl<Key>e: MenuItem(Mode.EditGame) \n \
455    :Ctrl<Key>E: MenuItem(Mode.EditPosition) \n \
456    :Meta<Key>O: MenuItem(View.EngineOutput) \n \
457    :Meta<Key>E: MenuItem(View.EvaluationGraph) \n \
458    :Meta<Key>G: MenuItem(View.GameList) \n \
459    :Meta<Key>H: MenuItem(View.MoveHistory) \n \
460    :<Key>Pause: MenuItem(Mode.Pause) \n \
461    :<Key>F3: MenuItem(Action.Accept) \n \
462    :<Key>F4: MenuItem(Action.Decline) \n \
463    :<Key>F12: MenuItem(Action.Rematch) \n \
464    :<Key>F5: MenuItem(Action.CallFlag) \n \
465    :<Key>F6: MenuItem(Action.Draw) \n \
466    :<Key>F7: MenuItem(Action.Adjourn) \n \
467    :<Key>F8: MenuItem(Action.Abort) \n \
468    :<Key>F10: MenuItem(Action.StopObserving) \n \
469    :<Key>F11: MenuItem(Action.StopExamining) \n \
470    :Ctrl<Key>d: MenuItem(DebugProc) \n \
471    :Meta Ctrl<Key>F12: MenuItem(DebugProc) \n \
472    :Meta<Key>End: MenuItem(Edit.ForwardtoEnd) \n \
473    :Meta<Key>Right: MenuItem(Edit.Forward) \n \
474    :Meta<Key>Home: MenuItem(Edit.BacktoStart) \n \
475    :Meta<Key>Left: MenuItem(Edit.Backward) \n \
476    :<Key>Left: MenuItem(Edit.Backward) \n \
477    :<Key>Right: MenuItem(Edit.Forward) \n \
478    :<Key>Home: MenuItem(Edit.Revert) \n \
479    :<Key>End: MenuItem(Edit.TruncateGame) \n \
480    :Ctrl<Key>m: MenuItem(Engine.MoveNow) \n \
481    :Ctrl<Key>x: MenuItem(Engine.RetractMove) \n \
482    :Meta<Key>J: MenuItem(Options.Adjudications) \n \
483    :Meta<Key>U: MenuItem(Options.CommonEngine) \n \
484    :Meta<Key>T: MenuItem(Options.TimeControl) \n \
485    :Ctrl<Key>P: MenuItem(PonderNextMove) \n "
486 #ifndef OPTIONSDIALOG
487     "\
488    :Ctrl<Key>Q: MenuItem(AlwaysQueenProc) \n \
489    :Ctrl<Key>F: MenuItem(AutoflagProc) \n \
490    :Ctrl<Key>A: MenuItem(AnimateMovingProc) \n \
491    :Ctrl<Key>L: MenuItem(TestLegalityProc) \n \
492    :Ctrl<Key>H: MenuItem(HideThinkingProc) \n "
493 #endif
494    "\
495    :<Key>F1: MenuItem(Help.ManXBoard) \n \
496    :<Key>F2: MenuItem(View.FlipView) \n \
497    :<KeyDown>Return: TempBackwardProc() \n \
498    :<KeyUp>Return: TempForwardProc() \n";
499
500 char ICSInputTranslations[] =
501     "<Key>Up: UpKeyProc() \n "
502     "<Key>Down: DownKeyProc() \n "
503     "<Key>Return: EnterKeyProc() \n";
504
505 // [HGM] vari: another hideous kludge: call extend-end first so we can be sure select-start works,
506 //             as the widget is destroyed before the up-click can call extend-end
507 char commentTranslations[] = "<Btn3Down>: extend-end() select-start() CommentClick() \n";
508
509 String xboardResources[] = {
510     "*Error*translations: #override\\n <Key>Return: ErrorPopDown()",
511     NULL
512   };
513
514
515 /* Max possible square size */
516 #define MAXSQSIZE 256
517
518 static int xpm_avail[MAXSQSIZE];
519
520 #ifdef HAVE_DIR_STRUCT
521
522 /* Extract piece size from filename */
523 static int
524 xpm_getsize (char *name, int len, char *ext)
525 {
526     char *p, *d;
527     char buf[10];
528
529     if (len < 4)
530       return 0;
531
532     if ((p=strchr(name, '.')) == NULL ||
533         StrCaseCmp(p+1, ext) != 0)
534       return 0;
535
536     p = name + 3;
537     d = buf;
538
539     while (*p && isdigit(*p))
540       *(d++) = *(p++);
541
542     *d = 0;
543     return atoi(buf);
544 }
545
546 /* Setup xpm_avail */
547 static int
548 xpm_getavail (char *dirname, char *ext)
549 {
550     DIR *dir;
551     struct dirent *ent;
552     int  i;
553
554     for (i=0; i<MAXSQSIZE; ++i)
555       xpm_avail[i] = 0;
556
557     if (appData.debugMode)
558       fprintf(stderr, "XPM dir:%s:ext:%s:\n", dirname, ext);
559
560     dir = opendir(dirname);
561     if (!dir)
562       {
563           fprintf(stderr, _("%s: Can't access XPM directory %s\n"),
564                   programName, dirname);
565           exit(1);
566       }
567
568     while ((ent=readdir(dir)) != NULL) {
569         i = xpm_getsize(ent->d_name, NAMLEN(ent), ext);
570         if (i > 0 && i < MAXSQSIZE)
571           xpm_avail[i] = 1;
572     }
573
574     closedir(dir);
575
576     return 0;
577 }
578
579 void
580 xpm_print_avail (FILE *fp, char *ext)
581 {
582     int i;
583
584     fprintf(fp, _("Available `%s' sizes:\n"), ext);
585     for (i=1; i<MAXSQSIZE; ++i) {
586         if (xpm_avail[i])
587           printf("%d\n", i);
588     }
589 }
590
591 /* Return XPM piecesize closest to size */
592 int
593 xpm_closest_to (char *dirname, int size, char *ext)
594 {
595     int i;
596     int sm_diff = MAXSQSIZE;
597     int sm_index = 0;
598     int diff;
599
600     xpm_getavail(dirname, ext);
601
602     if (appData.debugMode)
603       xpm_print_avail(stderr, ext);
604
605     for (i=1; i<MAXSQSIZE; ++i) {
606         if (xpm_avail[i]) {
607             diff = size - i;
608             diff = (diff<0) ? -diff : diff;
609             if (diff < sm_diff) {
610                 sm_diff = diff;
611                 sm_index = i;
612             }
613         }
614     }
615
616     if (!sm_index) {
617         fprintf(stderr, _("Error: No `%s' files!\n"), ext);
618         exit(1);
619     }
620
621     return sm_index;
622 }
623 #else   /* !HAVE_DIR_STRUCT */
624 /* If we are on a system without a DIR struct, we can't
625    read the directory, so we can't collect a list of
626    filenames, etc., so we can't do any size-fitting. */
627 int
628 xpm_closest_to (char *dirname, int size, char *ext)
629 {
630     fprintf(stderr, _("\
631 Warning: No DIR structure found on this system --\n\
632          Unable to autosize for XPM/XIM pieces.\n\
633    Please report this error to %s.\n\
634    Include system type & operating system in message.\n"), PACKAGE_BUGREPORT););
635     return size;
636 }
637 #endif /* HAVE_DIR_STRUCT */
638
639
640 /* Arrange to catch delete-window events */
641 Atom wm_delete_window;
642 void
643 CatchDeleteWindow (Widget w, String procname)
644 {
645   char buf[MSG_SIZ];
646   XSetWMProtocols(xDisplay, XtWindow(w), &wm_delete_window, 1);
647   snprintf(buf, sizeof(buf), "<Message>WM_PROTOCOLS: %s() \n", procname);
648   XtAugmentTranslations(w, XtParseTranslationTable(buf));
649 }
650
651 void
652 BoardToTop ()
653 {
654   Arg args[16];
655   XtSetArg(args[0], XtNiconic, False);
656   XtSetValues(shellWidget, args, 1);
657
658   XtPopup(shellWidget, XtGrabNone); /* Raise if lowered  */
659 }
660
661 //---------------------------------------------------------------------------------------------------------
662 // some symbol definitions to provide the proper (= XBoard) context for the code in args.h
663 #define XBOARD True
664 #define JAWS_ARGS
665 #define CW_USEDEFAULT (1<<31)
666 #define ICS_TEXT_MENU_SIZE 90
667 #define DEBUG_FILE "xboard.debug"
668 #define SetCurrentDirectory chdir
669 #define GetCurrentDirectory(SIZE, NAME) getcwd(NAME, SIZE)
670 #define OPTCHAR "-"
671 #define SEPCHAR " "
672
673 // The option definition and parsing code common to XBoard and WinBoard is collected in this file
674 #include "args.h"
675
676 // front-end part of option handling
677
678 // [HGM] This platform-dependent table provides the location for storing the color info
679 extern char *crWhite, * crBlack;
680
681 void *
682 colorVariable[] = {
683   &appData.whitePieceColor,
684   &appData.blackPieceColor,
685   &appData.lightSquareColor,
686   &appData.darkSquareColor,
687   &appData.highlightSquareColor,
688   &appData.premoveHighlightColor,
689   &appData.lowTimeWarningColor,
690   NULL,
691   NULL,
692   NULL,
693   NULL,
694   NULL,
695   &crWhite,
696   &crBlack,
697   NULL
698 };
699
700 // [HGM] font: keep a font for each square size, even non-stndard ones
701 #define NUM_SIZES 18
702 #define MAX_SIZE 130
703 Boolean fontIsSet[NUM_FONTS], fontValid[NUM_FONTS][MAX_SIZE];
704 char *fontTable[NUM_FONTS][MAX_SIZE];
705
706 void
707 ParseFont (char *name, int number)
708 { // in XBoard, only 2 of the fonts are currently implemented, and we just copy their name
709   int size;
710   if(sscanf(name, "size%d:", &size)) {
711     // [HGM] font: font is meant for specific boardSize (likely from settings file);
712     //       defer processing it until we know if it matches our board size
713     if(size >= 0 && size<MAX_SIZE) { // for now, fixed limit
714         fontTable[number][size] = strdup(strchr(name, ':')+1);
715         fontValid[number][size] = True;
716     }
717     return;
718   }
719   switch(number) {
720     case 0: // CLOCK_FONT
721         appData.clockFont = strdup(name);
722       break;
723     case 1: // MESSAGE_FONT
724         appData.font = strdup(name);
725       break;
726     case 2: // COORD_FONT
727         appData.coordFont = strdup(name);
728       break;
729     default:
730       return;
731   }
732   fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
733 }
734
735 void
736 SetFontDefaults ()
737 { // only 2 fonts currently
738   appData.clockFont = CLOCK_FONT_NAME;
739   appData.coordFont = COORD_FONT_NAME;
740   appData.font  =   DEFAULT_FONT_NAME;
741 }
742
743 void
744 CreateFonts ()
745 { // no-op, until we identify the code for this already in XBoard and move it here
746 }
747
748 void
749 ParseColor (int n, char *name)
750 { // in XBoard, just copy the color-name string
751   if(colorVariable[n]) *(char**)colorVariable[n] = strdup(name);
752 }
753
754 void
755 ParseTextAttribs (ColorClass cc, char *s)
756 {
757     (&appData.colorShout)[cc] = strdup(s);
758 }
759
760 void
761 ParseBoardSize (void *addr, char *name)
762 {
763     appData.boardSize = strdup(name);
764 }
765
766 void
767 LoadAllSounds ()
768 { // In XBoard the sound-playing program takes care of obtaining the actual sound
769 }
770
771 void
772 SetCommPortDefaults ()
773 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
774 }
775
776 // [HGM] args: these three cases taken out to stay in front-end
777 void
778 SaveFontArg (FILE *f, ArgDescriptor *ad)
779 {
780   char *name;
781   int i, n = (int)(intptr_t)ad->argLoc;
782   switch(n) {
783     case 0: // CLOCK_FONT
784         name = appData.clockFont;
785       break;
786     case 1: // MESSAGE_FONT
787         name = appData.font;
788       break;
789     case 2: // COORD_FONT
790         name = appData.coordFont;
791       break;
792     default:
793       return;
794   }
795   for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
796     if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
797         fontTable[n][squareSize] = strdup(name);
798         fontValid[n][squareSize] = True;
799         break;
800   }
801   for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
802     fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
803 }
804
805 void
806 ExportSounds ()
807 { // nothing to do, as the sounds are at all times represented by their text-string names already
808 }
809
810 void
811 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
812 {       // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
813         fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
814 }
815
816 void
817 SaveColor (FILE *f, ArgDescriptor *ad)
818 {       // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
819         if(colorVariable[(int)(intptr_t)ad->argLoc])
820         fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
821 }
822
823 void
824 SaveBoardSize (FILE *f, char *name, void *addr)
825 { // wrapper to shield back-end from BoardSize & sizeInfo
826   fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
827 }
828
829 void
830 ParseCommPortSettings (char *s)
831 { // no such option in XBoard (yet)
832 }
833
834 int frameX, frameY;
835
836 void
837 GetActualPlacement (Widget wg, WindowPlacement *wp)
838 {
839   XWindowAttributes winAt;
840   Window win, dummy;
841   int rx, ry;
842
843   if(!wg) return;
844
845   win = XtWindow(wg);
846   XGetWindowAttributes(xDisplay, win, &winAt); // this works, where XtGetValues on XtNx, XtNy does not!
847   XTranslateCoordinates (xDisplay, win, winAt.root, -winAt.border_width, -winAt.border_width, &rx, &ry, &dummy);
848   wp->x = rx - winAt.x;
849   wp->y = ry - winAt.y;
850   wp->height = winAt.height;
851   wp->width = winAt.width;
852   frameX = winAt.x; frameY = winAt.y; // remember to decide if windows touch
853 }
854
855 void
856 GetWindowCoords ()
857 { // wrapper to shield use of window handles from back-end (make addressible by number?)
858   // In XBoard this will have to wait until awareness of window parameters is implemented
859   GetActualPlacement(shellWidget, &wpMain);
860   if(shellUp[EngOutDlg]) GetActualPlacement(shells[EngOutDlg], &wpEngineOutput);
861   if(shellUp[HistoryDlg]) GetActualPlacement(shells[HistoryDlg], &wpMoveHistory);
862   if(shellUp[EvalGraphDlg]) GetActualPlacement(shells[EvalGraphDlg], &wpEvalGraph);
863   if(shellUp[GameListDlg]) GetActualPlacement(shells[GameListDlg], &wpGameList);
864   if(shellUp[CommentDlg]) GetActualPlacement(shells[CommentDlg], &wpComment);
865   if(shellUp[TagsDlg]) GetActualPlacement(shells[TagsDlg], &wpTags);
866 }
867
868 void
869 PrintCommPortSettings (FILE *f, char *name)
870 { // This option does not exist in XBoard
871 }
872
873 void
874 EnsureOnScreen (int *x, int *y, int minX, int minY)
875 {
876   return;
877 }
878
879 int
880 MainWindowUp ()
881 { // [HGM] args: allows testing if main window is realized from back-end
882   return xBoardWindow != 0;
883 }
884
885 void
886 SwitchWindow ()
887 {
888     extern Option dualOptions[];
889     static Window dual;
890     Window tmp = xBoardWindow;
891     if(!dual) dual = XtWindow(dualOptions[3].handle); // must be first call
892     xBoardWindow = dual; // swap them
893     dual = tmp;
894 }
895
896 void
897 PopUpStartupDialog ()
898 {  // start menu not implemented in XBoard
899 }
900
901 char *
902 ConvertToLine (int argc, char **argv)
903 {
904   static char line[128*1024], buf[1024];
905   int i;
906
907   line[0] = NULLCHAR;
908   for(i=1; i<argc; i++)
909     {
910       if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
911           && argv[i][0] != '{' )
912         snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
913       else
914         snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
915       strncat(line, buf, 128*1024 - strlen(line) - 1 );
916     }
917
918   line[strlen(line)-1] = NULLCHAR;
919   return line;
920 }
921
922 //--------------------------------------------------------------------------------------------
923
924 #define BoardSize int
925 void
926 InitDrawingSizes (BoardSize boardSize, int flags)
927 {   // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
928     Dimension boardWidth, boardHeight, w, h;
929     int i;
930     static Dimension oldWidth, oldHeight;
931     static VariantClass oldVariant;
932     static int oldMono = -1, oldTwoBoards = 0;
933
934     if(!formWidget) return;
935
936     if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
937     oldTwoBoards = twoBoards;
938
939     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
940     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
941     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
942
943   if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
944
945     oldWidth = boardWidth; oldHeight = boardHeight;
946     CreateGrid();
947
948     /*
949      * Inhibit shell resizing.
950      */
951     shellArgs[0].value = w = (XtArgVal) boardWidth + marginW ;
952     shellArgs[1].value = h = (XtArgVal) boardHeight + marginH;
953     shellArgs[4].value = shellArgs[2].value = w;
954     shellArgs[5].value = shellArgs[3].value = h;
955     XtSetValues(shellWidget, &shellArgs[0], 6);
956
957     XSync(xDisplay, False);
958     DelayedDrag();
959   }
960
961     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
962     // (only for xpm)
963
964   if(gameInfo.variant != oldVariant) { // and only if variant changed
965
966     if(useImages) {
967       for(i=0; i<4; i++) {
968         int p;
969         for(p=0; p<=(int)WhiteKing; p++)
970            xpmPieceBitmap[i][p] = xpmPieceBitmap2[i][p]; // defaults
971         if(gameInfo.variant == VariantShogi) {
972            xpmPieceBitmap[i][(int)WhiteCannon] = xpmPieceBitmap2[i][(int)WhiteKing+1];
973            xpmPieceBitmap[i][(int)WhiteNightrider] = xpmPieceBitmap2[i][(int)WhiteKing+2];
974            xpmPieceBitmap[i][(int)WhiteSilver] = xpmPieceBitmap2[i][(int)WhiteKing+3];
975            xpmPieceBitmap[i][(int)WhiteGrasshopper] = xpmPieceBitmap2[i][(int)WhiteKing+4];
976            xpmPieceBitmap[i][(int)WhiteQueen] = xpmPieceBitmap2[i][(int)WhiteLance];
977         }
978 #ifdef GOTHIC
979         if(gameInfo.variant == VariantGothic) {
980            xpmPieceBitmap[i][(int)WhiteMarshall] = xpmPieceBitmap2[i][(int)WhiteSilver];
981         }
982 #endif
983         if(gameInfo.variant == VariantSChess && (squareSize == 49 || squareSize == 72)) {
984            xpmPieceBitmap[i][(int)WhiteAngel]    = xpmPieceBitmap2[i][(int)WhiteFalcon];
985            xpmPieceBitmap[i][(int)WhiteMarshall] = xpmPieceBitmap2[i][(int)WhiteAlfil];
986         }
987 #if !HAVE_LIBXPM
988         // [HGM] why are thee ximMasks used at all? the ximPieceBitmaps seem to be never used!
989         for(p=0; p<=(int)WhiteKing; p++)
990            ximMaskPm[p] = ximMaskPm2[p]; // defaults
991         if(gameInfo.variant == VariantShogi) {
992            ximMaskPm[(int)WhiteCannon] = ximMaskPm2[(int)WhiteKing+1];
993            ximMaskPm[(int)WhiteNightrider] = ximMaskPm2[(int)WhiteKing+2];
994            ximMaskPm[(int)WhiteSilver] = ximMaskPm2[(int)WhiteKing+3];
995            ximMaskPm[(int)WhiteGrasshopper] = ximMaskPm2[(int)WhiteKing+4];
996            ximMaskPm[(int)WhiteQueen] = ximMaskPm2[(int)WhiteLance];
997         }
998 #ifdef GOTHIC
999         if(gameInfo.variant == VariantGothic) {
1000            ximMaskPm[(int)WhiteMarshall] = ximMaskPm2[(int)WhiteSilver];
1001         }
1002 #endif
1003         if(gameInfo.variant == VariantSChess && (squareSize == 49 || squareSize == 72)) {
1004            ximMaskPm[(int)WhiteAngel]    = ximMaskPm2[(int)WhiteFalcon];
1005            ximMaskPm[(int)WhiteMarshall] = ximMaskPm2[(int)WhiteAlfil];
1006         }
1007 #endif
1008       }
1009     } else {
1010       for(i=0; i<2; i++) {
1011         int p;
1012         for(p=0; p<=(int)WhiteKing; p++)
1013            pieceBitmap[i][p] = pieceBitmap2[i][p]; // defaults
1014         if(gameInfo.variant == VariantShogi) {
1015            pieceBitmap[i][(int)WhiteCannon] = pieceBitmap2[i][(int)WhiteKing+1];
1016            pieceBitmap[i][(int)WhiteNightrider] = pieceBitmap2[i][(int)WhiteKing+2];
1017            pieceBitmap[i][(int)WhiteSilver] = pieceBitmap2[i][(int)WhiteKing+3];
1018            pieceBitmap[i][(int)WhiteGrasshopper] = pieceBitmap2[i][(int)WhiteKing+4];
1019            pieceBitmap[i][(int)WhiteQueen] = pieceBitmap2[i][(int)WhiteLance];
1020         }
1021 #ifdef GOTHIC
1022         if(gameInfo.variant == VariantGothic) {
1023            pieceBitmap[i][(int)WhiteMarshall] = pieceBitmap2[i][(int)WhiteSilver];
1024         }
1025 #endif
1026         if(gameInfo.variant == VariantSChess && (squareSize == 49 || squareSize == 72)) {
1027            pieceBitmap[i][(int)WhiteAngel]    = pieceBitmap2[i][(int)WhiteFalcon];
1028            pieceBitmap[i][(int)WhiteMarshall] = pieceBitmap2[i][(int)WhiteAlfil];
1029         }
1030       }
1031     }
1032     oldMono = -10; // kludge to force recreation of animation masks
1033     oldVariant = gameInfo.variant;
1034   }
1035 #if HAVE_LIBXPM
1036   if(appData.monoMode != oldMono)
1037     CreateAnimVars();
1038 #endif
1039   oldMono = appData.monoMode;
1040 }
1041
1042 static int
1043 MakeOneColor (char *name, Pixel *color)
1044 {
1045     XrmValue vFrom, vTo;
1046     if (!appData.monoMode) {
1047         vFrom.addr = (caddr_t) name;
1048         vFrom.size = strlen(name);
1049         XtConvert(shellWidget, XtRString, &vFrom, XtRPixel, &vTo);
1050         if (vTo.addr == NULL) {
1051           appData.monoMode = True;
1052           return True;
1053         } else {
1054           *color = *(Pixel *) vTo.addr;
1055         }
1056     }
1057     return False;
1058 }
1059
1060 static int
1061 MakeColors ()
1062 {   // [HGM] taken out of main(), so it can be called from BoardOptions dialog
1063     int forceMono = False;
1064
1065     forceMono |= MakeOneColor(appData.lightSquareColor, &lightSquareColor);
1066     forceMono |= MakeOneColor(appData.darkSquareColor, &darkSquareColor);
1067     forceMono |= MakeOneColor(appData.whitePieceColor, &whitePieceColor);
1068     forceMono |= MakeOneColor(appData.blackPieceColor, &blackPieceColor);
1069     forceMono |= MakeOneColor(appData.highlightSquareColor, &highlightSquareColor);
1070     forceMono |= MakeOneColor(appData.premoveHighlightColor, &premoveHighlightColor);
1071     if (appData.lowTimeWarning)
1072         forceMono |= MakeOneColor(appData.lowTimeWarningColor, &lowTimeWarningColor);
1073     if(appData.dialogColor[0]) MakeOneColor(appData.dialogColor, &dialogColor);
1074     if(appData.buttonColor[0]) MakeOneColor(appData.buttonColor, &buttonColor);
1075
1076     return forceMono;
1077 }
1078
1079 static void
1080 CreateAnyPieces ()
1081 {   // [HGM] taken out of main
1082 #if HAVE_LIBXPM
1083     if (appData.monoMode && // [HGM] no sense to go on to certain doom
1084        (appData.bitmapDirectory == NULL || appData.bitmapDirectory[0] == NULLCHAR))
1085             appData.bitmapDirectory = strdup(DEF_BITMAP_DIR);
1086
1087     if (appData.bitmapDirectory[0] != NULLCHAR) {
1088       CreatePieces();
1089     } else {
1090       CreateXPMPieces();
1091       CreateXPMBoard(appData.liteBackTextureFile, 1);
1092       CreateXPMBoard(appData.darkBackTextureFile, 0);
1093     }
1094 #else
1095     CreateXIMPieces();
1096     /* Create regular pieces */
1097     if (!useImages) CreatePieces();
1098 #endif
1099 }
1100
1101 void
1102 InitDrawingParams ()
1103 {
1104     MakeColors(); CreateGCs(True);
1105     CreateAnyPieces();
1106 }
1107
1108 void
1109 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
1110 {   // detervtomine what fonts to use, and create them
1111     XrmValue vTo;
1112     XrmDatabase xdb;
1113
1114     if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
1115         appData.clockFont = fontTable[CLOCK_FONT][squareSize];
1116     if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
1117         appData.font = fontTable[MESSAGE_FONT][squareSize];
1118     if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
1119         appData.coordFont = fontTable[COORD_FONT][squareSize];
1120
1121 #if ENABLE_NLS
1122     appData.font = InsertPxlSize(appData.font, fontPxlSize);
1123     appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
1124     appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
1125     fontSet = CreateFontSet(appData.font);
1126     clockFontSet = CreateFontSet(appData.clockFont);
1127     {
1128       /* For the coordFont, use the 0th font of the fontset. */
1129       XFontSet coordFontSet = CreateFontSet(appData.coordFont);
1130       XFontStruct **font_struct_list;
1131       XFontSetExtents *fontSize;
1132       char **font_name_list;
1133       XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
1134       coordFontID = XLoadFont(xDisplay, font_name_list[0]);
1135       coordFontStruct = XQueryFont(xDisplay, coordFontID);
1136       fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
1137       textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
1138     }
1139 #else
1140     appData.font = FindFont(appData.font, fontPxlSize);
1141     appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
1142     appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
1143     clockFontID = XLoadFont(xDisplay, appData.clockFont);
1144     clockFontStruct = XQueryFont(xDisplay, clockFontID);
1145     coordFontID = XLoadFont(xDisplay, appData.coordFont);
1146     coordFontStruct = XQueryFont(xDisplay, coordFontID);
1147     // textHeight in !NLS mode!
1148 #endif
1149     countFontID = coordFontID;  // [HGM] holdings
1150     countFontStruct = coordFontStruct;
1151
1152     xdb = XtDatabase(xDisplay);
1153 #if ENABLE_NLS
1154     XrmPutLineResource(&xdb, "*international: True");
1155     vTo.size = sizeof(XFontSet);
1156     vTo.addr = (XtPointer) &fontSet;
1157     XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
1158 #else
1159     XrmPutStringResource(&xdb, "*font", appData.font);
1160 #endif
1161 }
1162
1163 char *
1164 PrintArg (ArgType t)
1165 {
1166   char *p="";
1167   switch(t) {
1168     case ArgZ:
1169     case ArgInt:      p = " N"; break;
1170     case ArgString:   p = " STR"; break;
1171     case ArgBoolean:  p = " TF"; break;
1172     case ArgSettingsFilename:
1173     case ArgFilename: p = " FILE"; break;
1174     case ArgX:        p = " Nx"; break;
1175     case ArgY:        p = " Ny"; break;
1176     case ArgAttribs:  p = " TEXTCOL"; break;
1177     case ArgColor:    p = " COL"; break;
1178     case ArgFont:     p = " FONT"; break;
1179     case ArgBoardSize: p = " SIZE"; break;
1180     case ArgFloat: p = " FLOAT"; break;
1181     case ArgTrue:
1182     case ArgFalse:
1183     case ArgTwo:
1184     case ArgNone:
1185     case ArgCommSettings:
1186       break;
1187   }
1188   return p;
1189 }
1190
1191 void
1192 PrintOptions ()
1193 {
1194   char buf[MSG_SIZ];
1195   int len=0;
1196   ArgDescriptor *q, *p = argDescriptors+5;
1197   printf("\nXBoard accepts the following options:\n"
1198          "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
1199          " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
1200          " SIZE = board-size spec(s)\n"
1201          " Within parentheses are short forms, or options to set to true or false.\n"
1202          " Persistent options (saved in the settings file) are marked with *)\n\n");
1203   while(p->argName) {
1204     if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
1205     snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
1206     if(p->save) strcat(buf+len, "*");
1207     for(q=p+1; q->argLoc == p->argLoc; q++) {
1208       if(q->argName[0] == '-') continue;
1209       strcat(buf+len, q == p+1 ? " (" : " ");
1210       sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
1211     }
1212     if(q != p+1) strcat(buf+len, ")");
1213     len = strlen(buf);
1214     if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
1215     p = q;
1216   }
1217   if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
1218 }
1219
1220 int
1221 main (int argc, char **argv)
1222 {
1223     int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
1224     XSetWindowAttributes window_attributes;
1225     Arg args[16];
1226     Dimension boardWidth, boardHeight, w, h;
1227     char *p;
1228     int forceMono = False;
1229
1230     srandom(time(0)); // [HGM] book: make random truly random
1231
1232     setbuf(stdout, NULL);
1233     setbuf(stderr, NULL);
1234     debugFP = stderr;
1235
1236     if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
1237         printf("%s version %s\n", PACKAGE_NAME, PACKAGE_VERSION);
1238         exit(0);
1239     }
1240
1241     if(argc > 1 && !strcmp(argv[1], "--help" )) {
1242         PrintOptions();
1243         exit(0);
1244     }
1245
1246     programName = strrchr(argv[0], '/');
1247     if (programName == NULL)
1248       programName = argv[0];
1249     else
1250       programName++;
1251
1252 #ifdef ENABLE_NLS
1253     XtSetLanguageProc(NULL, NULL, NULL);
1254     if (appData.debugMode) {
1255       fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
1256     }
1257
1258     bindtextdomain(PACKAGE, LOCALEDIR);
1259     textdomain(PACKAGE);
1260 #endif
1261
1262     appData.boardSize = "";
1263     InitAppData(ConvertToLine(argc, argv));
1264     p = getenv("HOME");
1265     if (p == NULL) p = "/tmp";
1266     i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
1267     gameCopyFilename = (char*) malloc(i);
1268     gamePasteFilename = (char*) malloc(i);
1269     snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
1270     snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
1271
1272     { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
1273         static char buf[MSG_SIZ];
1274         EscapeExpand(buf, appData.firstInitString);
1275         appData.firstInitString = strdup(buf);
1276         EscapeExpand(buf, appData.secondInitString);
1277         appData.secondInitString = strdup(buf);
1278         EscapeExpand(buf, appData.firstComputerString);
1279         appData.firstComputerString = strdup(buf);
1280         EscapeExpand(buf, appData.secondComputerString);
1281         appData.secondComputerString = strdup(buf);
1282     }
1283
1284     if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
1285         chessDir = ".";
1286     } else {
1287         if (chdir(chessDir) != 0) {
1288             fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
1289             perror(chessDir);
1290             exit(1);
1291         }
1292     }
1293
1294     if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
1295         /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
1296         if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL)  {
1297            printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
1298            exit(errno);
1299         }
1300         setbuf(debugFP, NULL);
1301     }
1302
1303     /* [HGM,HR] make sure board size is acceptable */
1304     if(appData.NrFiles > BOARD_FILES ||
1305        appData.NrRanks > BOARD_RANKS   )
1306          DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
1307
1308 #if !HIGHDRAG
1309     /* This feature does not work; animation needs a rewrite */
1310     appData.highlightDragging = FALSE;
1311 #endif
1312     InitBackEnd1();
1313
1314         gameInfo.variant = StringToVariant(appData.variant);
1315         InitPosition(FALSE);
1316
1317     shellWidget =
1318       XtAppInitialize(&appContext, "XBoard", shellOptions,
1319                       XtNumber(shellOptions),
1320                       &argc, argv, xboardResources, NULL, 0);
1321
1322     XtGetApplicationResources(shellWidget, (XtPointer) &appData,
1323                               clientResources, XtNumber(clientResources),
1324                               NULL, 0);
1325
1326     xDisplay = XtDisplay(shellWidget);
1327     xScreen = DefaultScreen(xDisplay);
1328     wm_delete_window = XInternAtom(xDisplay, "WM_DELETE_WINDOW", True);
1329
1330     /*
1331      * determine size, based on supplied or remembered -size, or screen size
1332      */
1333     if (isdigit(appData.boardSize[0])) {
1334         i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
1335                    &lineGap, &clockFontPxlSize, &coordFontPxlSize,
1336                    &fontPxlSize, &smallLayout, &tinyLayout);
1337         if (i == 0) {
1338             fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
1339                     programName, appData.boardSize);
1340             exit(2);
1341         }
1342         if (i < 7) {
1343             /* Find some defaults; use the nearest known size */
1344             SizeDefaults *szd, *nearest;
1345             int distance = 99999;
1346             nearest = szd = sizeDefaults;
1347             while (szd->name != NULL) {
1348                 if (abs(szd->squareSize - squareSize) < distance) {
1349                     nearest = szd;
1350                     distance = abs(szd->squareSize - squareSize);
1351                     if (distance == 0) break;
1352                 }
1353                 szd++;
1354             }
1355             if (i < 2) lineGap = nearest->lineGap;
1356             if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
1357             if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
1358             if (i < 5) fontPxlSize = nearest->fontPxlSize;
1359             if (i < 6) smallLayout = nearest->smallLayout;
1360             if (i < 7) tinyLayout = nearest->tinyLayout;
1361         }
1362     } else {
1363         SizeDefaults *szd = sizeDefaults;
1364         if (*appData.boardSize == NULLCHAR) {
1365             while (DisplayWidth(xDisplay, xScreen) < szd->minScreenSize ||
1366                    DisplayHeight(xDisplay, xScreen) < szd->minScreenSize) {
1367               szd++;
1368             }
1369             if (szd->name == NULL) szd--;
1370             appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
1371         } else {
1372             while (szd->name != NULL &&
1373                    StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
1374             if (szd->name == NULL) {
1375                 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
1376                         programName, appData.boardSize);
1377                 exit(2);
1378             }
1379         }
1380         squareSize = szd->squareSize;
1381         lineGap = szd->lineGap;
1382         clockFontPxlSize = szd->clockFontPxlSize;
1383         coordFontPxlSize = szd->coordFontPxlSize;
1384         fontPxlSize = szd->fontPxlSize;
1385         smallLayout = szd->smallLayout;
1386         tinyLayout = szd->tinyLayout;
1387         // [HGM] font: use defaults from settings file if available and not overruled
1388     }
1389
1390     /* Now, using squareSize as a hint, find a good XPM/XIM set size */
1391     if (strlen(appData.pixmapDirectory) > 0) {
1392         p = ExpandPathName(appData.pixmapDirectory);
1393         if (!p) {
1394             fprintf(stderr, _("Error expanding path name \"%s\"\n"),
1395                    appData.pixmapDirectory);
1396             exit(1);
1397         }
1398         if (appData.debugMode) {
1399           fprintf(stderr, _("\
1400 XBoard square size (hint): %d\n\
1401 %s fulldir:%s:\n"), squareSize, IMAGE_EXT, p);
1402         }
1403         squareSize = xpm_closest_to(p, squareSize, IMAGE_EXT);
1404         if (appData.debugMode) {
1405             fprintf(stderr, _("Closest %s size: %d\n"), IMAGE_EXT, squareSize);
1406         }
1407     }
1408     defaultLineGap = lineGap;
1409     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
1410
1411     /* [HR] height treated separately (hacked) */
1412     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
1413     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1414
1415     /*
1416      * Determine what fonts to use.
1417      */
1418     InitializeFonts(clockFontPxlSize, coordFontPxlSize, fontPxlSize);
1419
1420     /*
1421      * Detect if there are not enough colors available and adapt.
1422      */
1423     if (DefaultDepth(xDisplay, xScreen) <= 2) {
1424       appData.monoMode = True;
1425     }
1426
1427     forceMono = MakeColors();
1428
1429     if (forceMono) {
1430       fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1431               programName);
1432         appData.monoMode = True;
1433     }
1434
1435     if (appData.monoMode && appData.debugMode) {
1436         fprintf(stderr, _("white pixel = 0x%lx, black pixel = 0x%lx\n"),
1437                 (unsigned long) XWhitePixel(xDisplay, xScreen),
1438                 (unsigned long) XBlackPixel(xDisplay, xScreen));
1439     }
1440
1441     ParseIcsTextColors();
1442
1443     XtAppAddActions(appContext, boardActions, XtNumber(boardActions));
1444
1445     /*
1446      * widget hierarchy
1447      */
1448     if (tinyLayout) {
1449         layoutName = "tinyLayout";
1450     } else if (smallLayout) {
1451         layoutName = "smallLayout";
1452     } else {
1453         layoutName = "normalLayout";
1454     }
1455
1456     optList = BoardPopUp(squareSize, lineGap, (void*)
1457 #if ENABLE_NLS
1458                                                 &clockFontSet);
1459 #else
1460                                                 clockFontStruct);
1461 #endif
1462     boardWidget      = optList[W_BOARD].handle;
1463     menuBarWidget    = optList[W_MENU].handle;
1464     dropMenu         = optList[W_DROP].handle;
1465     titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1466     formWidget  = XtParent(boardWidget);
1467     XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1468     XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1469     XtGetValues(optList[W_WHITE].handle, args, 2);
1470     if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1471       XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1472       XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1473       XtGetValues(optList[W_PAUSE].handle, args, 2);
1474     }
1475     AppendEnginesToMenu(appData.recentEngineList);
1476
1477     xBoardWindow = XtWindow(boardWidget);
1478
1479     // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1480     //       not need to go into InitDrawingSizes().
1481
1482     /*
1483      * Create X checkmark bitmap and initialize option menu checks.
1484      */
1485     ReadBitmap(&xMarkPixmap, "checkmark.bm",
1486                checkmark_bits, checkmark_width, checkmark_height);
1487     InitMenuMarkers();
1488
1489     /*
1490      * Create an icon.
1491      */
1492     ReadBitmap(&wIconPixmap, "icon_white.bm",
1493                icon_white_bits, icon_white_width, icon_white_height);
1494     ReadBitmap(&bIconPixmap, "icon_black.bm",
1495                icon_black_bits, icon_black_width, icon_black_height);
1496     iconPixmap = wIconPixmap;
1497     i = 0;
1498     XtSetArg(args[i], XtNiconPixmap, iconPixmap);  i++;
1499     XtSetValues(shellWidget, args, i);
1500
1501     /*
1502      * Create a cursor for the board widget.
1503      */
1504     window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1505     XChangeWindowAttributes(xDisplay, xBoardWindow,
1506                             CWCursor, &window_attributes);
1507
1508     /*
1509      * Inhibit shell resizing.
1510      */
1511     shellArgs[0].value = (XtArgVal) &w;
1512     shellArgs[1].value = (XtArgVal) &h;
1513     XtGetValues(shellWidget, shellArgs, 2);
1514     shellArgs[4].value = shellArgs[2].value = w;
1515     shellArgs[5].value = shellArgs[3].value = h;
1516     XtSetValues(shellWidget, &shellArgs[2], 4);
1517     marginW =  w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1518     marginH =  h - boardHeight;
1519
1520     CatchDeleteWindow(shellWidget, "QuitProc");
1521
1522     CreateGCs(False);
1523     CreateGrid();
1524     CreateAnyPieces();
1525
1526     if(appData.logoSize)
1527     {   // locate and read user logo
1528         char buf[MSG_SIZ];
1529         snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1530         ASSIGN(userLogo, buf);
1531     }
1532
1533     if (appData.animate || appData.animateDragging)
1534       CreateAnimVars();
1535
1536     XtAugmentTranslations(formWidget,
1537                           XtParseTranslationTable(globalTranslations));
1538
1539     XtAddEventHandler(formWidget, KeyPressMask, False,
1540                       (XtEventHandler) MoveTypeInProc, NULL);
1541     XtAddEventHandler(shellWidget, StructureNotifyMask, False,
1542                       (XtEventHandler) EventProc, NULL);
1543
1544     /* [AS] Restore layout */
1545     if( wpMoveHistory.visible ) {
1546       HistoryPopUp();
1547     }
1548
1549     if( wpEvalGraph.visible )
1550       {
1551         EvalGraphPopUp();
1552       };
1553
1554     if( wpEngineOutput.visible ) {
1555       EngineOutputPopUp();
1556     }
1557
1558     InitBackEnd2();
1559
1560     if (errorExitStatus == -1) {
1561         if (appData.icsActive) {
1562             /* We now wait until we see "login:" from the ICS before
1563                sending the logon script (problems with timestamp otherwise) */
1564             /*ICSInitScript();*/
1565             if (appData.icsInputBox) ICSInputBoxPopUp();
1566         }
1567
1568     #ifdef SIGWINCH
1569     signal(SIGWINCH, TermSizeSigHandler);
1570     #endif
1571         signal(SIGINT, IntSigHandler);
1572         signal(SIGTERM, IntSigHandler);
1573         if (*appData.cmailGameName != NULLCHAR) {
1574             signal(SIGUSR1, CmailSigHandler);
1575         }
1576     }
1577
1578     gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1579     InitPosition(TRUE);
1580     UpdateLogos(TRUE);
1581 //    XtSetKeyboardFocus(shellWidget, formWidget);
1582     XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1583
1584     XtAppMainLoop(appContext);
1585     if (appData.debugMode) fclose(debugFP); // [DM] debug
1586     return 0;
1587 }
1588
1589 RETSIGTYPE
1590 TermSizeSigHandler (int sig)
1591 {
1592     update_ics_width();
1593 }
1594
1595 RETSIGTYPE
1596 IntSigHandler (int sig)
1597 {
1598     ExitEvent(sig);
1599 }
1600
1601 RETSIGTYPE
1602 CmailSigHandler (int sig)
1603 {
1604     int dummy = 0;
1605     int error;
1606
1607     signal(SIGUSR1, SIG_IGN);   /* suspend handler     */
1608
1609     /* Activate call-back function CmailSigHandlerCallBack()             */
1610     OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1611
1612     signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1613 }
1614
1615 void
1616 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1617 {
1618     BoardToTop();
1619     ReloadCmailMsgEvent(TRUE);  /* Reload cmail msg  */
1620 }
1621 /**** end signal code ****/
1622
1623
1624 #define Abs(n) ((n)<0 ? -(n) : (n))
1625
1626 #ifdef ENABLE_NLS
1627 char *
1628 InsertPxlSize (char *pattern, int targetPxlSize)
1629 {
1630     char *base_fnt_lst, strInt[12], *p, *q;
1631     int alternatives, i, len, strIntLen;
1632
1633     /*
1634      * Replace the "*" (if present) in the pixel-size slot of each
1635      * alternative with the targetPxlSize.
1636      */
1637     p = pattern;
1638     alternatives = 1;
1639     while ((p = strchr(p, ',')) != NULL) {
1640       alternatives++;
1641       p++;
1642     }
1643     snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1644     strIntLen = strlen(strInt);
1645     base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1646
1647     p = pattern;
1648     q = base_fnt_lst;
1649     while (alternatives--) {
1650       char *comma = strchr(p, ',');
1651       for (i=0; i<14; i++) {
1652         char *hyphen = strchr(p, '-');
1653         if (!hyphen) break;
1654         if (comma && hyphen > comma) break;
1655         len = hyphen + 1 - p;
1656         if (i == 7 && *p == '*' && len == 2) {
1657           p += len;
1658           memcpy(q, strInt, strIntLen);
1659           q += strIntLen;
1660           *q++ = '-';
1661         } else {
1662           memcpy(q, p, len);
1663           p += len;
1664           q += len;
1665         }
1666       }
1667       if (!comma) break;
1668       len = comma + 1 - p;
1669       memcpy(q, p, len);
1670       p += len;
1671       q += len;
1672     }
1673     strcpy(q, p);
1674
1675     return base_fnt_lst;
1676 }
1677
1678 XFontSet
1679 CreateFontSet (char *base_fnt_lst)
1680 {
1681     XFontSet fntSet;
1682     char **missing_list;
1683     int missing_count;
1684     char *def_string;
1685
1686     fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1687                             &missing_list, &missing_count, &def_string);
1688     if (appData.debugMode) {
1689       int i, count;
1690       XFontStruct **font_struct_list;
1691       char **font_name_list;
1692       fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1693       if (fntSet) {
1694         fprintf(debugFP, " got list %s, locale %s\n",
1695                 XBaseFontNameListOfFontSet(fntSet),
1696                 XLocaleOfFontSet(fntSet));
1697         count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1698         for (i = 0; i < count; i++) {
1699           fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1700         }
1701       }
1702       for (i = 0; i < missing_count; i++) {
1703         fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1704       }
1705     }
1706     if (fntSet == NULL) {
1707       fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1708       exit(2);
1709     }
1710     return fntSet;
1711 }
1712 #else // not ENABLE_NLS
1713 /*
1714  * Find a font that matches "pattern" that is as close as
1715  * possible to the targetPxlSize.  Prefer fonts that are k
1716  * pixels smaller to fonts that are k pixels larger.  The
1717  * pattern must be in the X Consortium standard format,
1718  * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1719  * The return value should be freed with XtFree when no
1720  * longer needed.
1721  */
1722 char *
1723 FindFont (char *pattern, int targetPxlSize)
1724 {
1725     char **fonts, *p, *best, *scalable, *scalableTail;
1726     int i, j, nfonts, minerr, err, pxlSize;
1727
1728     fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1729     if (nfonts < 1) {
1730         fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1731                 programName, pattern);
1732         exit(2);
1733     }
1734
1735     best = fonts[0];
1736     scalable = NULL;
1737     minerr = 999999;
1738     for (i=0; i<nfonts; i++) {
1739         j = 0;
1740         p = fonts[i];
1741         if (*p != '-') continue;
1742         while (j < 7) {
1743             if (*p == NULLCHAR) break;
1744             if (*p++ == '-') j++;
1745         }
1746         if (j < 7) continue;
1747         pxlSize = atoi(p);
1748         if (pxlSize == 0) {
1749             scalable = fonts[i];
1750             scalableTail = p;
1751         } else {
1752             err = pxlSize - targetPxlSize;
1753             if (Abs(err) < Abs(minerr) ||
1754                 (minerr > 0 && err < 0 && -err == minerr)) {
1755                 best = fonts[i];
1756                 minerr = err;
1757             }
1758         }
1759     }
1760     if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1761         /* If the error is too big and there is a scalable font,
1762            use the scalable font. */
1763         int headlen = scalableTail - scalable;
1764         p = (char *) XtMalloc(strlen(scalable) + 10);
1765         while (isdigit(*scalableTail)) scalableTail++;
1766         sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1767     } else {
1768         p = (char *) XtMalloc(strlen(best) + 2);
1769         safeStrCpy(p, best, strlen(best)+1 );
1770     }
1771     if (appData.debugMode) {
1772         fprintf(debugFP, _("resolved %s at pixel size %d\n  to %s\n"),
1773                 pattern, targetPxlSize, p);
1774     }
1775     XFreeFontNames(fonts);
1776     return p;
1777 }
1778 #endif
1779
1780 void
1781 DeleteGCs ()
1782 {   // [HGM] deletes GCs that are to be remade, to prevent resource leak;
1783     // must be called before all non-first callse to CreateGCs()
1784     XtReleaseGC(shellWidget, highlineGC);
1785     XtReleaseGC(shellWidget, lightSquareGC);
1786     XtReleaseGC(shellWidget, darkSquareGC);
1787     XtReleaseGC(shellWidget, lineGC);
1788     if (appData.monoMode) {
1789         if (DefaultDepth(xDisplay, xScreen) == 1) {
1790             XtReleaseGC(shellWidget, wbPieceGC);
1791         } else {
1792             XtReleaseGC(shellWidget, bwPieceGC);
1793         }
1794     } else {
1795         XtReleaseGC(shellWidget, prelineGC);
1796         XtReleaseGC(shellWidget, wdPieceGC);
1797         XtReleaseGC(shellWidget, wlPieceGC);
1798         XtReleaseGC(shellWidget, bdPieceGC);
1799         XtReleaseGC(shellWidget, blPieceGC);
1800     }
1801 }
1802
1803 static GC
1804 CreateOneGC (XGCValues *gc_values, Pixel foreground, Pixel background)
1805 {
1806     XtGCMask value_mask = GCLineWidth | GCLineStyle | GCForeground
1807       | GCBackground | GCFunction | GCPlaneMask;
1808     gc_values->foreground = foreground;
1809     gc_values->background = background;
1810     return XtGetGC(shellWidget, value_mask, gc_values);
1811 }
1812
1813 static void
1814 CreateGCs (int redo)
1815 {
1816     XGCValues gc_values;
1817     GC copyInvertedGC;
1818     Pixel white = XWhitePixel(xDisplay, xScreen);
1819     Pixel black = XBlackPixel(xDisplay, xScreen);
1820
1821     gc_values.plane_mask = AllPlanes;
1822     gc_values.line_width = lineGap;
1823     gc_values.line_style = LineSolid;
1824     gc_values.function = GXcopy;
1825
1826   if(redo) {
1827     DeleteGCs(); // called a second time; clean up old GCs first
1828   } else { // [HGM] grid and font GCs created on first call only
1829     coordGC = CreateOneGC(&gc_values, black, white);
1830     XSetFont(xDisplay, coordGC, coordFontID);
1831
1832     // [HGM] make font for holdings counts (white on black)
1833     countGC = CreateOneGC(&gc_values, white, black);
1834     XSetFont(xDisplay, countGC, countFontID);
1835   }
1836     lineGC = CreateOneGC(&gc_values, black, black);
1837
1838     if (appData.monoMode) {
1839
1840         highlineGC = CreateOneGC(&gc_values, white, white);
1841         lightSquareGC = wbPieceGC = CreateOneGC(&gc_values, white, black);
1842         darkSquareGC = bwPieceGC = CreateOneGC(&gc_values, black, white);
1843
1844         if (DefaultDepth(xDisplay, xScreen) == 1) {
1845             /* Avoid XCopyPlane on 1-bit screens to work around Sun bug */
1846             gc_values.function = GXcopyInverted;
1847             copyInvertedGC = CreateOneGC(&gc_values, black, white);
1848             gc_values.function = GXcopy;
1849             if (XBlackPixel(xDisplay, xScreen) == 1) {
1850                 bwPieceGC = darkSquareGC;
1851                 wbPieceGC = copyInvertedGC;
1852             } else {
1853                 bwPieceGC = copyInvertedGC;
1854                 wbPieceGC = lightSquareGC;
1855             }
1856         }
1857     } else {
1858
1859         highlineGC = CreateOneGC(&gc_values, highlightSquareColor, highlightSquareColor);
1860         prelineGC = CreateOneGC(&gc_values, premoveHighlightColor, premoveHighlightColor);
1861         lightSquareGC = CreateOneGC(&gc_values, lightSquareColor, darkSquareColor);
1862         darkSquareGC = CreateOneGC(&gc_values, darkSquareColor, lightSquareColor);
1863         wdPieceGC = CreateOneGC(&gc_values, whitePieceColor, darkSquareColor);
1864         wlPieceGC = CreateOneGC(&gc_values, whitePieceColor, lightSquareColor);
1865         bdPieceGC = CreateOneGC(&gc_values, blackPieceColor, darkSquareColor);
1866         blPieceGC = CreateOneGC(&gc_values, blackPieceColor, lightSquareColor);
1867     }
1868 }
1869
1870 void
1871 loadXIM (XImage *xim, XImage *xmask, char *filename, Pixmap *dest, Pixmap *mask)
1872 {
1873     int x, y, w, h, p;
1874     FILE *fp;
1875     Pixmap temp;
1876     XGCValues   values;
1877     GC maskGC;
1878
1879     fp = fopen(filename, "rb");
1880     if (!fp) {
1881         fprintf(stderr, _("%s: error loading XIM!\n"), programName);
1882         exit(1);
1883     }
1884
1885     w = fgetc(fp);
1886     h = fgetc(fp);
1887
1888     for (y=0; y<h; ++y) {
1889         for (x=0; x<h; ++x) {
1890             p = fgetc(fp);
1891
1892             switch (p) {
1893               case 0:
1894                 XPutPixel(xim, x, y, blackPieceColor);
1895                 if (xmask)
1896                   XPutPixel(xmask, x, y, WhitePixel(xDisplay,xScreen));
1897                 break;
1898               case 1:
1899                 XPutPixel(xim, x, y, darkSquareColor);
1900                 if (xmask)
1901                   XPutPixel(xmask, x, y, BlackPixel(xDisplay,xScreen));
1902                 break;
1903               case 2:
1904                 XPutPixel(xim, x, y, whitePieceColor);
1905                 if (xmask)
1906                   XPutPixel(xmask, x, y, WhitePixel(xDisplay,xScreen));
1907                 break;
1908               case 3:
1909                 XPutPixel(xim, x, y, lightSquareColor);
1910                 if (xmask)
1911                   XPutPixel(xmask, x, y, BlackPixel(xDisplay,xScreen));
1912                 break;
1913             }
1914         }
1915     }
1916
1917     fclose(fp);
1918
1919     /* create Pixmap of piece */
1920     *dest = XCreatePixmap(xDisplay, DefaultRootWindow(xDisplay),
1921                           w, h, xim->depth);
1922     XPutImage(xDisplay, *dest, lightSquareGC, xim,
1923               0, 0, 0, 0, w, h);
1924
1925     /* create Pixmap of clipmask
1926        Note: We assume the white/black pieces have the same
1927              outline, so we make only 6 masks. This is okay
1928              since the XPM clipmask routines do the same. */
1929     if (xmask) {
1930       temp = XCreatePixmap(xDisplay, DefaultRootWindow(xDisplay),
1931                             w, h, xim->depth);
1932       XPutImage(xDisplay, temp, lightSquareGC, xmask,
1933               0, 0, 0, 0, w, h);
1934
1935       /* now create the 1-bit version */
1936       *mask = XCreatePixmap(xDisplay, DefaultRootWindow(xDisplay),
1937                           w, h, 1);
1938
1939       values.foreground = 1;
1940       values.background = 0;
1941
1942       /* Don't use XtGetGC, not read only */
1943       maskGC = XCreateGC(xDisplay, *mask,
1944                     GCForeground | GCBackground, &values);
1945       XCopyPlane(xDisplay, temp, *mask, maskGC,
1946                   0, 0, squareSize, squareSize, 0, 0, 1);
1947       XFreePixmap(xDisplay, temp);
1948     }
1949 }
1950
1951
1952 char pieceBitmapNames[] = "pnbrqfeacwmohijgdvlsukpnsl";
1953
1954 void
1955 CreateXIMPieces ()
1956 {
1957     int piece, kind;
1958     char buf[MSG_SIZ];
1959     u_int ss;
1960     static char *ximkind[] = { "ll", "ld", "dl", "dd" };
1961     XImage *ximtemp;
1962
1963     ss = squareSize;
1964
1965     /* The XSynchronize calls were copied from CreatePieces.
1966        Not sure if needed, but can't hurt */
1967     XSynchronize(xDisplay, True); /* Work-around for xlib/xt
1968                                      buffering bug */
1969
1970     /* temp needed by loadXIM() */
1971     ximtemp = XGetImage(xDisplay, DefaultRootWindow(xDisplay),
1972                  0, 0, ss, ss, AllPlanes, XYPixmap);
1973
1974     if (strlen(appData.pixmapDirectory) == 0) {
1975       useImages = 0;
1976     } else {
1977         useImages = 1;
1978         if (appData.monoMode) {
1979           DisplayFatalError(_("XIM pieces cannot be used in monochrome mode"),
1980                             0, 2);
1981           ExitEvent(2);
1982         }
1983         fprintf(stderr, _("\nLoading XIMs...\n"));
1984         /* Load pieces */
1985         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
1986             fprintf(stderr, "%d", piece+1);
1987             for (kind=0; kind<4; kind++) {
1988                 fprintf(stderr, ".");
1989                 snprintf(buf, sizeof(buf), "%s/%s%c%s%u.xim",
1990                         ExpandPathName(appData.pixmapDirectory),
1991                         piece <= (int) WhiteKing ? "" : "w",
1992                         pieceBitmapNames[piece],
1993                         ximkind[kind], ss);
1994                 ximPieceBitmap[kind][piece] =
1995                   XGetImage(xDisplay, DefaultRootWindow(xDisplay),
1996                             0, 0, ss, ss, AllPlanes, XYPixmap);
1997                 if (appData.debugMode)
1998                   fprintf(stderr, _("(File:%s:) "), buf);
1999                 loadXIM(ximPieceBitmap[kind][piece],
2000                         ximtemp, buf,
2001                         &(xpmPieceBitmap2[kind][piece]),
2002                         &(ximMaskPm2[piece]));
2003                 if(piece <= (int)WhiteKing)
2004                     xpmPieceBitmap[kind][piece] = xpmPieceBitmap2[kind][piece];
2005             }
2006             fprintf(stderr," ");
2007         }
2008         /* Load light and dark squares */
2009         /* If the LSQ and DSQ pieces don't exist, we will
2010            draw them with solid squares. */
2011         snprintf(buf,sizeof(buf), "%s/lsq%u.xim", ExpandPathName(appData.pixmapDirectory), ss);
2012         if (access(buf, 0) != 0) {
2013             useImageSqs = 0;
2014         } else {
2015             useImageSqs = 1;
2016             fprintf(stderr, _("light square "));
2017             ximLightSquare=
2018               XGetImage(xDisplay, DefaultRootWindow(xDisplay),
2019                         0, 0, ss, ss, AllPlanes, XYPixmap);
2020             if (appData.debugMode)
2021               fprintf(stderr, _("(File:%s:) "), buf);
2022
2023             loadXIM(ximLightSquare, NULL, buf, &xpmLightSquare, NULL);
2024             fprintf(stderr, _("dark square "));
2025             snprintf(buf,sizeof(buf), "%s/dsq%u.xim",
2026                     ExpandPathName(appData.pixmapDirectory), ss);
2027             if (appData.debugMode)
2028               fprintf(stderr, _("(File:%s:) "), buf);
2029             ximDarkSquare=
2030               XGetImage(xDisplay, DefaultRootWindow(xDisplay),
2031                         0, 0, ss, ss, AllPlanes, XYPixmap);
2032             loadXIM(ximDarkSquare, NULL, buf, &xpmDarkSquare, NULL);
2033             xpmJailSquare = xpmLightSquare;
2034         }
2035         fprintf(stderr, _("Done.\n"));
2036     }
2037     XSynchronize(xDisplay, False); /* Work-around for xlib/xt buffering bug */
2038 }
2039
2040 static VariantClass oldVariant = (VariantClass) -1; // [HGM] pieces: redo every time variant changes
2041
2042 #if HAVE_LIBXPM
2043 void
2044 CreateXPMBoard (char *s, int kind)
2045 {
2046     XpmAttributes attr;
2047     attr.valuemask = 0;
2048     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
2049     if (XpmReadFileToPixmap(xDisplay, xBoardWindow, s, &(xpmBoardBitmap[kind]), NULL, &attr) == 0) {
2050         useTexture |= kind + 1; textureW[kind] = attr.width; textureH[kind] = attr.height;
2051     }
2052 }
2053
2054 void
2055 FreeXPMPieces ()
2056 {   // [HGM] to prevent resoucre leak on calling CreaeXPMPieces() a second time,
2057     // thisroutine has to be called t free the old piece pixmaps
2058     int piece, kind;
2059     for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++)
2060         for (kind=0; kind<4; kind++) XFreePixmap(xDisplay, xpmPieceBitmap2[kind][piece]);
2061     if(useImageSqs) {
2062         XFreePixmap(xDisplay, xpmLightSquare);
2063         XFreePixmap(xDisplay, xpmDarkSquare);
2064     }
2065 }
2066
2067 void
2068 CreateXPMPieces ()
2069 {
2070     int piece, kind, r;
2071     char buf[MSG_SIZ];
2072     u_int ss = squareSize;
2073     XpmAttributes attr;
2074     static char *xpmkind[] = { "ll", "ld", "dl", "dd" };
2075     XpmColorSymbol symbols[4];
2076     static int redo = False;
2077
2078     if(redo) FreeXPMPieces(); else redo = 1;
2079
2080     /* The XSynchronize calls were copied from CreatePieces.
2081        Not sure if needed, but can't hurt */
2082     XSynchronize(xDisplay, True); /* Work-around for xlib/xt buffering bug */
2083
2084     /* Setup translations so piece colors match square colors */
2085     symbols[0].name = "light_piece";
2086     symbols[0].value = appData.whitePieceColor;
2087     symbols[1].name = "dark_piece";
2088     symbols[1].value = appData.blackPieceColor;
2089     symbols[2].name = "light_square";
2090     symbols[2].value = appData.lightSquareColor;
2091     symbols[3].name = "dark_square";
2092     symbols[3].value = appData.darkSquareColor;
2093
2094     attr.valuemask = XpmColorSymbols;
2095     attr.colorsymbols = symbols;
2096     attr.numsymbols = 4;
2097
2098     if (appData.monoMode) {
2099       DisplayFatalError(_("XPM pieces cannot be used in monochrome mode"),
2100                         0, 2);
2101       ExitEvent(2);
2102     }
2103     if (strlen(appData.pixmapDirectory) == 0) {
2104         XpmPieces* pieces = builtInXpms;
2105         useImages = 1;
2106         /* Load pieces */
2107         while (pieces->size != squareSize && pieces->size) pieces++;
2108         if (!pieces->size) {
2109           fprintf(stderr, _("No builtin XPM pieces of size %d\n"), squareSize);
2110           exit(1);
2111         }
2112         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
2113             for (kind=0; kind<4; kind++) {
2114
2115                 if ((r=XpmCreatePixmapFromData(xDisplay, xBoardWindow,
2116                                                pieces->xpm[piece][kind],
2117                                                &(xpmPieceBitmap2[kind][piece]),
2118                                                NULL, &attr)) != 0) {
2119                   fprintf(stderr, _("Error %d loading XPM image \"%s\"\n"),
2120                           r, buf);
2121                   exit(1);
2122                 }
2123                 if(piece <= (int) WhiteKing)
2124                     xpmPieceBitmap[kind][piece] = xpmPieceBitmap2[kind][piece];
2125             }
2126         }
2127         useImageSqs = 0;
2128         xpmJailSquare = xpmLightSquare;
2129     } else {
2130         useImages = 1;
2131
2132         fprintf(stderr, _("\nLoading XPMs...\n"));
2133
2134         /* Load pieces */
2135         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
2136             fprintf(stderr, "%d ", piece+1);
2137             for (kind=0; kind<4; kind++) {
2138               snprintf(buf, sizeof(buf), "%s/%s%c%s%u.xpm",
2139                         ExpandPathName(appData.pixmapDirectory),
2140                         piece > (int) WhiteKing ? "w" : "",
2141                         pieceBitmapNames[piece],
2142                         xpmkind[kind], ss);
2143                 if (appData.debugMode) {
2144                     fprintf(stderr, _("(File:%s:) "), buf);
2145                 }
2146                 if ((r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
2147                                            &(xpmPieceBitmap2[kind][piece]),
2148                                            NULL, &attr)) != 0) {
2149                     if(piece != (int)WhiteKing && piece > (int)WhiteQueen) {
2150                       // [HGM] missing: read of unorthodox piece failed; substitute King.
2151                       snprintf(buf, sizeof(buf), "%s/k%s%u.xpm",
2152                                 ExpandPathName(appData.pixmapDirectory),
2153                                 xpmkind[kind], ss);
2154                         if (appData.debugMode) {
2155                             fprintf(stderr, _("(Replace by File:%s:) "), buf);
2156                         }
2157                         r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
2158                                                 &(xpmPieceBitmap2[kind][piece]),
2159                                                 NULL, &attr);
2160                     }
2161                     if (r != 0) {
2162                         fprintf(stderr, _("Error %d loading XPM file \"%s\"\n"),
2163                                 r, buf);
2164                         exit(1);
2165                     }
2166                 }
2167                 if(piece <= (int) WhiteKing)
2168                     xpmPieceBitmap[kind][piece] = xpmPieceBitmap2[kind][piece];
2169             }
2170         }
2171         /* Load light and dark squares */
2172         /* If the LSQ and DSQ pieces don't exist, we will
2173            draw them with solid squares. */
2174         fprintf(stderr, _("light square "));
2175         snprintf(buf, sizeof(buf), "%s/lsq%u.xpm", ExpandPathName(appData.pixmapDirectory), ss);
2176         if (access(buf, 0) != 0) {
2177             useImageSqs = 0;
2178         } else {
2179             useImageSqs = 1;
2180             if (appData.debugMode)
2181               fprintf(stderr, _("(File:%s:) "), buf);
2182
2183             if ((r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
2184                                        &xpmLightSquare, NULL, &attr)) != 0) {
2185                 fprintf(stderr, _("Error %d loading XPM file \"%s\"\n"), r, buf);
2186                 exit(1);
2187             }
2188             fprintf(stderr, _("dark square "));
2189             snprintf(buf, sizeof(buf), "%s/dsq%u.xpm",
2190                     ExpandPathName(appData.pixmapDirectory), ss);
2191             if (appData.debugMode) {
2192                 fprintf(stderr, _("(File:%s:) "), buf);
2193             }
2194             if ((r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
2195                                        &xpmDarkSquare, NULL, &attr)) != 0) {
2196                 fprintf(stderr, _("Error %d loading XPM file \"%s\"\n"), r, buf);
2197                 exit(1);
2198             }
2199         }
2200         xpmJailSquare = xpmLightSquare;
2201         fprintf(stderr, _("Done.\n"));
2202     }
2203     oldVariant = -1; // kludge to force re-makig of animation masks
2204     XSynchronize(xDisplay, False); /* Work-around for xlib/xt
2205                                       buffering bug */
2206 }
2207 #endif /* HAVE_LIBXPM */
2208
2209 #if HAVE_LIBXPM
2210 /* No built-in bitmaps */
2211 void CreatePieces()
2212 {
2213     int piece, kind;
2214     char buf[MSG_SIZ];
2215     u_int ss = squareSize;
2216
2217     XSynchronize(xDisplay, True); /* Work-around for xlib/xt
2218                                      buffering bug */
2219
2220     for (kind = SOLID; kind <= (appData.monoMode ? OUTLINE : SOLID); kind++) {
2221         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
2222           snprintf(buf, MSG_SIZ, "%s%c%u%c.bm", piece > (int)WhiteKing ? "w" : "",
2223                    pieceBitmapNames[piece],
2224                    ss, kind == SOLID ? 's' : 'o');
2225           ReadBitmap(&pieceBitmap2[kind][piece], buf, NULL, ss, ss);
2226           if(piece <= (int)WhiteKing)
2227             pieceBitmap[kind][piece] = pieceBitmap2[kind][piece];
2228         }
2229     }
2230
2231     XSynchronize(xDisplay, False); /* Work-around for xlib/xt
2232                                       buffering bug */
2233 }
2234 #else
2235 /* With built-in bitmaps */
2236 void
2237 CreatePieces ()
2238 {
2239     BuiltInBits* bib = builtInBits;
2240     int piece, kind;
2241     char buf[MSG_SIZ];
2242     u_int ss = squareSize;
2243
2244     XSynchronize(xDisplay, True); /* Work-around for xlib/xt
2245                                      buffering bug */
2246
2247     while (bib->squareSize != ss && bib->squareSize != 0) bib++;
2248
2249     for (kind = SOLID; kind <= (appData.monoMode ? OUTLINE : SOLID); kind++) {
2250         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
2251           snprintf(buf, MSG_SIZ, "%s%c%u%c.bm", piece > (int)WhiteKing ? "w" : "",
2252                    pieceBitmapNames[piece],
2253                    ss, kind == SOLID ? 's' : 'o');
2254           ReadBitmap(&pieceBitmap2[kind][piece], buf,
2255                      bib->bits[kind][piece], ss, ss);
2256           if(piece <= (int)WhiteKing)
2257             pieceBitmap[kind][piece] = pieceBitmap2[kind][piece];
2258         }
2259     }
2260
2261     XSynchronize(xDisplay, False); /* Work-around for xlib/xt
2262                                       buffering bug */
2263 }
2264 #endif
2265
2266 void
2267 ReadBitmap (Pixmap *pm, String name, unsigned char bits[], u_int wreq, u_int hreq)
2268 {
2269     int x_hot, y_hot;
2270     u_int w, h;
2271     int errcode;
2272     char msg[MSG_SIZ], fullname[MSG_SIZ];
2273
2274     if (*appData.bitmapDirectory != NULLCHAR) {
2275       safeStrCpy(fullname, appData.bitmapDirectory, sizeof(fullname)/sizeof(fullname[0]) );
2276       strncat(fullname, "/", MSG_SIZ - strlen(fullname) - 1);
2277       strncat(fullname, name, MSG_SIZ - strlen(fullname) - 1);
2278       errcode = XReadBitmapFile(xDisplay, xBoardWindow, fullname,
2279                                 &w, &h, pm, &x_hot, &y_hot);
2280       fprintf(stderr, "load %s\n", name);
2281         if (errcode != BitmapSuccess) {
2282             switch (errcode) {
2283               case BitmapOpenFailed:
2284                 snprintf(msg, sizeof(msg), _("Can't open bitmap file %s"), fullname);
2285                 break;
2286               case BitmapFileInvalid:
2287                 snprintf(msg, sizeof(msg), _("Invalid bitmap in file %s"), fullname);
2288                 break;
2289               case BitmapNoMemory:
2290                 snprintf(msg, sizeof(msg), _("Ran out of memory reading bitmap file %s"),
2291                         fullname);
2292                 break;
2293               default:
2294                 snprintf(msg, sizeof(msg), _("Unknown XReadBitmapFile error %d on file %s"),
2295                         errcode, fullname);
2296                 break;
2297             }
2298             fprintf(stderr, _("%s: %s...using built-in\n"),
2299                     programName, msg);
2300         } else if (w != wreq || h != hreq) {
2301             fprintf(stderr,
2302                     _("%s: Bitmap %s is %dx%d, not %dx%d...using built-in\n"),
2303                     programName, fullname, w, h, wreq, hreq);
2304         } else {
2305             return;
2306         }
2307     }
2308     if (bits != NULL) {
2309         *pm = XCreateBitmapFromData(xDisplay, xBoardWindow, (char *) bits,
2310                                     wreq, hreq);
2311     }
2312 }
2313
2314 void
2315 CreateGrid ()
2316 {
2317     int i, j;
2318
2319     if (lineGap == 0) return;
2320
2321     /* [HR] Split this into 2 loops for non-square boards. */
2322
2323     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
2324         gridSegments[i].x1 = 0;
2325         gridSegments[i].x2 =
2326           lineGap + BOARD_WIDTH * (squareSize + lineGap);
2327         gridSegments[i].y1 = gridSegments[i].y2
2328           = lineGap / 2 + (i * (squareSize + lineGap));
2329     }
2330
2331     for (j = 0; j < BOARD_WIDTH + 1; j++) {
2332         gridSegments[j + i].y1 = 0;
2333         gridSegments[j + i].y2 =
2334           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
2335         gridSegments[j + i].x1 = gridSegments[j + i].x2
2336           = lineGap / 2 + (j * (squareSize + lineGap));
2337     }
2338 }
2339
2340 void
2341 MarkMenuItem (char *menuRef, int state)
2342 {
2343     MenuItem *item = MenuNameToItem(menuRef);
2344
2345     if(item) {
2346         Arg args[2];
2347         XtSetArg(args[0], XtNleftBitmap, state ? xMarkPixmap : None);
2348         XtSetValues(item->handle, args, 1);
2349     }
2350 }
2351
2352 void
2353 EnableNamedMenuItem (char *menuRef, int state)
2354 {
2355     MenuItem *item = MenuNameToItem(menuRef);
2356
2357     if(item) XtSetSensitive(item->handle, state);
2358 }
2359
2360 void
2361 EnableButtonBar (int state)
2362 {
2363     XtSetSensitive(optList[W_BUTTON].handle, state);
2364 }
2365
2366
2367 void
2368 SetMenuEnables (Enables *enab)
2369 {
2370   while (enab->name != NULL) {
2371     EnableNamedMenuItem(enab->name, enab->value);
2372     enab++;
2373   }
2374 }
2375
2376 void
2377 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2378 {   // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
2379     MenuItem *item;
2380     if(*nprms == 0) return;
2381     item = MenuNameToItem(prms[0]);
2382     if(item) ((MenuProc *) item->proc) ();
2383 }
2384
2385 static void
2386 MenuEngineSelect (Widget w, caddr_t addr, caddr_t index)
2387 {
2388     RecentEngineEvent((int) (intptr_t) addr);
2389 }
2390
2391 void
2392 AppendMenuItem (char *msg, int n)
2393 {
2394     CreateMenuItem((Widget) optList[W_ENGIN].textValue, msg, (XtCallbackProc) MenuEngineSelect, n);
2395 }
2396
2397 void
2398 SetupDropMenu ()
2399 {
2400     int i, j, count;
2401     char label[32];
2402     Arg args[16];
2403     Widget entry;
2404     char* p;
2405
2406     for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
2407         entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
2408         p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
2409                    dmEnables[i].piece);
2410         XtSetSensitive(entry, p != NULL || !appData.testLegality
2411                        /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
2412                                        && !appData.icsActive));
2413         count = 0;
2414         while (p && *p++ == dmEnables[i].piece) count++;
2415         snprintf(label, sizeof(label), "%s  %d", dmEnables[i].widget, count);
2416         j = 0;
2417         XtSetArg(args[j], XtNlabel, label); j++;
2418         XtSetValues(entry, args, j);
2419     }
2420 }
2421
2422
2423 static void
2424 do_flash_delay (unsigned long msec)
2425 {
2426     TimeDelay(msec);
2427 }
2428
2429 static cairo_surface_t *cs; // to keep out of back-end :-(
2430
2431 void
2432 DrawBorder (int x, int y, int type)
2433 {
2434     cairo_t *cr;
2435     DrawSeekOpen();
2436
2437     cr = cairo_create(cs);
2438     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
2439     cairo_rectangle(cr, x, y, squareSize+lineGap, squareSize+lineGap);
2440     SetPen(cr, lineGap, type == 1 ? appData.highlightSquareColor : appData.premoveHighlightColor, 0);
2441     cairo_stroke(cr);
2442
2443     DrawSeekClose();
2444 }
2445
2446 static int
2447 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
2448 {
2449     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
2450     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
2451     *x0 = 0; *y0 = 0;
2452     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
2453     if(textureW[kind] < W*squareSize)
2454         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
2455     else
2456         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
2457     if(textureH[kind] < H*squareSize)
2458         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
2459     else
2460         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
2461     return 1;
2462 }
2463
2464 void
2465 DrawLogo (void *handle, void *logo)
2466 {
2467     cairo_surface_t *img, *cs;
2468     cairo_t *cr;
2469     int w, h;
2470
2471     if(!logo || !handle) return;
2472     cs = cairo_xlib_surface_create(xDisplay, XtWindow(handle), DefaultVisual(xDisplay, 0), appData.logoSize, appData.logoSize/2);
2473     img = cairo_image_surface_create_from_png (logo);
2474     w = cairo_image_surface_get_width (img);
2475     h = cairo_image_surface_get_height (img);
2476     cr = cairo_create(cs);
2477     cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
2478     cairo_set_source_surface (cr, img, 0, 0);
2479     cairo_paint (cr);
2480     cairo_destroy (cr);
2481     cairo_surface_destroy (img);
2482     cairo_surface_destroy (cs);
2483 }
2484
2485 static void
2486 BlankSquare (int x, int y, int color, ChessSquare piece, Drawable dest, int fac)
2487 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
2488     int x0, y0;
2489     if (useImages && color != 2 && (useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
2490         XCopyArea(xDisplay, xpmBoardBitmap[color], dest, wlPieceGC, x0, y0,
2491                   squareSize, squareSize, x*fac, y*fac);
2492     } else
2493     if (useImages && useImageSqs) {
2494         Pixmap pm;
2495         switch (color) {
2496           case 1: /* light */
2497             pm = xpmLightSquare;
2498             break;
2499           case 0: /* dark */
2500             pm = xpmDarkSquare;
2501             break;
2502           case 2: /* neutral */
2503           default:
2504             pm = xpmJailSquare; // [HGM] this is wrong, but apparently never used?
2505             break;
2506         }
2507         XCopyArea(xDisplay, pm, dest, wlPieceGC, 0, 0,
2508                   squareSize, squareSize, x*fac, y*fac);
2509     } else {
2510         GC gc;
2511         switch (color) {
2512           case 1: /* light */
2513             gc = lightSquareGC;
2514             break;
2515           case 0: /* dark */
2516             gc = darkSquareGC;
2517             break;
2518           case 2: /* neutral */
2519           default:
2520             gc = lineGC;
2521             break;
2522         }
2523         XFillRectangle(xDisplay, dest, gc, x*fac, y*fac, squareSize, squareSize);
2524     }
2525 }
2526
2527 /*
2528    I split out the routines to draw a piece so that I could
2529    make a generic flash routine.
2530 */
2531 static void
2532 monoDrawPiece_1bit (ChessSquare piece, int square_color, int x, int y, Drawable dest)
2533 {
2534     /* Avoid XCopyPlane on 1-bit screens to work around Sun bug */
2535     switch (square_color) {
2536       case 1: /* light */
2537       case 2: /* neutral */
2538       default:
2539         XCopyArea(xDisplay, (int) piece < (int) BlackPawn
2540                   ? *pieceToOutline(piece)
2541                   : *pieceToSolid(piece),
2542                   dest, bwPieceGC, 0, 0,
2543                   squareSize, squareSize, x, y);
2544         break;
2545       case 0: /* dark */
2546         XCopyArea(xDisplay, (int) piece < (int) BlackPawn
2547                   ? *pieceToSolid(piece)
2548                   : *pieceToOutline(piece),
2549                   dest, wbPieceGC, 0, 0,
2550                   squareSize, squareSize, x, y);
2551         break;
2552     }
2553 }
2554
2555 static void
2556 monoDrawPiece (ChessSquare piece, int square_color, int x, int y, Drawable dest)
2557 {
2558     switch (square_color) {
2559       case 1: /* light */
2560       case 2: /* neutral */
2561       default:
2562         XCopyPlane(xDisplay, (int) piece < (int) BlackPawn
2563                    ? *pieceToOutline(piece)
2564                    : *pieceToSolid(piece),
2565                    dest, bwPieceGC, 0, 0,
2566                    squareSize, squareSize, x, y, 1);
2567         break;
2568       case 0: /* dark */
2569         XCopyPlane(xDisplay, (int) piece < (int) BlackPawn
2570                    ? *pieceToSolid(piece)
2571                    : *pieceToOutline(piece),
2572                    dest, wbPieceGC, 0, 0,
2573                    squareSize, squareSize, x, y, 1);
2574         break;
2575     }
2576 }
2577
2578 static void
2579 colorDrawPiece (ChessSquare piece, int square_color, int x, int y, Drawable dest)
2580 {
2581     if(pieceToSolid(piece) == NULL) return; // [HGM] bitmaps: make it non-fatal if we have no bitmap;
2582     switch (square_color) {
2583       case 1: /* light */
2584         XCopyPlane(xDisplay, *pieceToSolid(piece),
2585                    dest, (int) piece < (int) BlackPawn
2586                    ? wlPieceGC : blPieceGC, 0, 0,
2587                    squareSize, squareSize, x, y, 1);
2588         break;
2589       case 0: /* dark */
2590         XCopyPlane(xDisplay, *pieceToSolid(piece),
2591                    dest, (int) piece < (int) BlackPawn
2592                    ? wdPieceGC : bdPieceGC, 0, 0,
2593                    squareSize, squareSize, x, y, 1);
2594         break;
2595       case 2: /* neutral */
2596       default:
2597         break; // should never contain pieces
2598     }
2599 }
2600
2601 static void
2602 colorDrawPieceImage (ChessSquare piece, int square_color, int x, int y, Drawable dest)
2603 {
2604     int kind, p = piece;
2605
2606     switch (square_color) {
2607       case 1: /* light */
2608       case 2: /* neutral */
2609       default:
2610         if ((int)piece < (int) BlackPawn) {
2611             kind = 0;
2612         } else {
2613             kind = 2;
2614             piece -= BlackPawn;
2615         }
2616         break;
2617       case 0: /* dark */
2618         if ((int)piece < (int) BlackPawn) {
2619             kind = 1;
2620         } else {
2621             kind = 3;
2622             piece -= BlackPawn;
2623         }
2624         break;
2625     }
2626     if(appData.upsideDown && flipView) { kind ^= 2; p += p < BlackPawn ? BlackPawn : -BlackPawn; }// swap white and black pieces
2627     if(useTexture & square_color+1) {
2628         BlankSquare(x, y, square_color, piece, dest, 1); // erase previous contents with background
2629         XSetClipMask(xDisplay, wlPieceGC, xpmMask[p]);
2630         XSetClipOrigin(xDisplay, wlPieceGC, x, y);
2631         XCopyArea(xDisplay, xpmPieceBitmap[kind][piece], dest, wlPieceGC, 0, 0, squareSize, squareSize, x, y);
2632         XSetClipMask(xDisplay, wlPieceGC, None);
2633         XSetClipOrigin(xDisplay, wlPieceGC, 0, 0);
2634     } else
2635     XCopyArea(xDisplay, xpmPieceBitmap[kind][piece],
2636               dest, wlPieceGC, 0, 0,
2637               squareSize, squareSize, x, y);
2638 }
2639
2640 typedef void (*DrawFunc)();
2641
2642 DrawFunc
2643 ChooseDrawFunc ()
2644 {
2645     if (appData.monoMode) {
2646         if (DefaultDepth(xDisplay, xScreen) == 1) {
2647             return monoDrawPiece_1bit;
2648         } else {
2649             return monoDrawPiece;
2650         }
2651     } else {
2652         if (useImages)
2653           return colorDrawPieceImage;
2654         else
2655           return colorDrawPiece;
2656     }
2657 }
2658
2659 void
2660 DrawDot (int marker, int x, int y, int r)
2661 {
2662         cairo_t *cr;
2663         DrawSeekOpen();
2664         cr = cairo_create(cs);
2665         cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
2666         if(appData.monoMode) {
2667             SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
2668             cairo_stroke_preserve(cr);
2669             SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
2670         } else {
2671             SetPen(cr, 2, marker == 2 ? "#FF0000" : "#FFFF00", 0);
2672         }
2673         cairo_fill(cr);
2674         cairo_stroke(cr);
2675
2676         cairo_destroy(cr);
2677         DrawSeekClose();
2678 }
2679
2680 void
2681 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *string, int align)
2682 {   // basic front-end board-draw function: takes care of everything that can be in square:
2683     // piece, background, coordinate/count, marker dot
2684     int direction, font_ascent, font_descent;
2685     XCharStruct overall;
2686     DrawFunc drawfunc;
2687
2688     if (piece == EmptySquare) {
2689         BlankSquare(x, y, square_color, piece, xBoardWindow, 1);
2690     } else {
2691         drawfunc = ChooseDrawFunc();
2692         drawfunc(piece, square_color, x, y, xBoardWindow);
2693     }
2694
2695     if(align) { // square carries inscription (coord or piece count)
2696         int xx = x, yy = y;
2697         GC hGC = align < 3 ? coordGC : countGC;
2698         // first calculate where it goes
2699         XTextExtents(countFontStruct, string, 1, &direction,
2700                          &font_ascent, &font_descent, &overall);
2701         if (align == 1) {
2702             xx += squareSize - overall.width - 2;
2703             yy += squareSize - font_descent - 1;
2704         } else if (align == 2) {
2705             xx += 2, yy += font_ascent + 1;
2706         } else if (align == 3) {
2707             xx += squareSize - overall.width - 2;
2708             yy += font_ascent + 1;
2709         } else if (align == 4) {
2710             xx += 2, yy += font_ascent + 1;
2711         }
2712         // then draw it
2713         if (appData.monoMode) {
2714             XDrawImageString(xDisplay, xBoardWindow, hGC, xx, yy, string, 1);
2715         } else {
2716             XDrawString(xDisplay, xBoardWindow, hGC, xx, yy, string, 1);
2717         }
2718     }
2719
2720     if(marker) { // print fat marker dot, if requested
2721         DrawDot(marker, x + squareSize/4, y+squareSize/4, squareSize/2);
2722     }
2723 }
2724
2725 void
2726 FlashDelay (int flash_delay)
2727 {
2728         XSync(xDisplay, False);
2729         if(flash_delay) do_flash_delay(flash_delay);
2730 }
2731
2732 double
2733 Fraction (int x, int start, int stop)
2734 {
2735    double f = ((double) x - start)/(stop - start);
2736    if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
2737    return f;
2738 }
2739
2740 static WindowPlacement wpNew;
2741
2742 void
2743 CoDrag (Widget sh, WindowPlacement *wp)
2744 {
2745     Arg args[16];
2746     int j=0, touch=0, fudge = 2;
2747     GetActualPlacement(sh, wp);
2748     if(abs(wpMain.x + wpMain.width + 2*frameX - wp->x)         < fudge) touch = 1; else // right touch
2749     if(abs(wp->x + wp->width + 2*frameX - wpMain.x)            < fudge) touch = 2; else // left touch
2750     if(abs(wpMain.y + wpMain.height + frameX + frameY - wp->y) < fudge) touch = 3; else // bottom touch
2751     if(abs(wp->y + wp->height + frameX + frameY - wpMain.y)    < fudge) touch = 4;      // top touch
2752     if(!touch ) return; // only windows that touch co-move
2753     if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
2754         int heightInc = wpNew.height - wpMain.height;
2755         double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
2756         double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
2757         wp->y += fracTop * heightInc;
2758         heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
2759         if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
2760     } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
2761         int widthInc = wpNew.width - wpMain.width;
2762         double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
2763         double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
2764         wp->y += fracLeft * widthInc;
2765         widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
2766         if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
2767     }
2768     wp->x += wpNew.x - wpMain.x;
2769     wp->y += wpNew.y - wpMain.y;
2770     if(touch == 1) wp->x += wpNew.width - wpMain.width; else
2771     if(touch == 3) wp->y += wpNew.height - wpMain.height;
2772     XtSetArg(args[j], XtNx, wp->x); j++;
2773     XtSetArg(args[j], XtNy, wp->y); j++;
2774     XtSetValues(sh, args, j);
2775 }
2776
2777 static XtIntervalId delayedDragID = 0;
2778
2779 void
2780 DragProc ()
2781 {
2782         GetActualPlacement(shellWidget, &wpNew);
2783         if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
2784            wpNew.width == wpMain.width && wpNew.height == wpMain.height) // not sized
2785             return; // false alarm
2786         if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
2787         if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
2788         if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
2789         if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
2790         wpMain = wpNew;
2791         DrawPosition(True, NULL);
2792         delayedDragID = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
2793 }
2794
2795
2796 void
2797 DelayedDrag ()
2798 {
2799     if(delayedDragID) XtRemoveTimeOut(delayedDragID); // cancel pending
2800     delayedDragID =
2801       XtAppAddTimeOut(appContext, 50, (XtTimerCallbackProc) DragProc, (XtPointer) 0); // and schedule new one 50 msec later
2802 }
2803
2804 void
2805 EventProc (Widget widget, caddr_t unused, XEvent *event)
2806 {
2807     if(XtIsRealized(widget) && event->type == ConfigureNotify || appData.useStickyWindows)
2808         DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
2809 }
2810
2811 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
2812
2813 float
2814 Color (char *col, int n)
2815 {
2816   int c;
2817   sscanf(col, "#%x", &c);
2818   c = c >> 4*n & 255;
2819   return c/255.;
2820 }
2821
2822 void
2823 SetPen (cairo_t *cr, float w, char *col, int dash)
2824 {
2825   static const double dotted[] = {4.0, 4.0};
2826   static int len  = sizeof(dotted) / sizeof(dotted[0]);
2827   cairo_set_line_width (cr, w);
2828   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
2829   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
2830 }
2831
2832 void DrawSeekAxis( int x, int y, int xTo, int yTo )
2833 {
2834     cairo_t *cr;
2835
2836     /* get a cairo_t */
2837     cr = cairo_create (cs);
2838
2839     cairo_move_to (cr, x, y);
2840     cairo_line_to(cr, xTo, yTo );
2841
2842     SetPen(cr, 2, "#000000", 0);
2843     cairo_stroke(cr);
2844
2845     /* free memory */
2846     cairo_destroy (cr);
2847 }
2848
2849 void DrawSeekBackground( int left, int top, int right, int bottom )
2850 {
2851     cairo_t *cr = cairo_create (cs);
2852
2853     cairo_rectangle (cr, left, top, right-left, bottom-top);
2854
2855     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
2856     cairo_fill(cr);
2857
2858     /* free memory */
2859     cairo_destroy (cr);
2860 }
2861
2862 void DrawSeekText(char *buf, int x, int y)
2863 {
2864     cairo_t *cr = cairo_create (cs);
2865
2866     cairo_select_font_face (cr, "Sans",
2867                             CAIRO_FONT_SLANT_NORMAL,
2868                             CAIRO_FONT_WEIGHT_NORMAL);
2869
2870     cairo_set_font_size (cr, 12.0);
2871
2872     cairo_move_to (cr, x, y+4);
2873     cairo_show_text( cr, buf);
2874
2875     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
2876     cairo_stroke(cr);
2877
2878     /* free memory */
2879     cairo_destroy (cr);
2880 }
2881
2882 void DrawSeekDot(int x, int y, int colorNr)
2883 {
2884     cairo_t *cr = cairo_create (cs);
2885     int square = colorNr & 0x80;
2886     colorNr &= 0x7F;
2887
2888     if(square)
2889         cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*squareSize/9, 2*squareSize/9);
2890     else
2891         cairo_arc(cr, x, y, squareSize/8, 0.0, 2*M_PI);
2892
2893     SetPen(cr, 2, "#000000", 0);
2894     cairo_stroke_preserve(cr);
2895     switch (colorNr) {
2896       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
2897       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
2898       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
2899     }
2900     cairo_fill(cr);
2901
2902     /* free memory */
2903     cairo_destroy (cr);
2904 }
2905
2906 void
2907 DrawSeekOpen ()
2908 {
2909     int boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
2910     int boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
2911     cs = cairo_xlib_surface_create(xDisplay, xBoardWindow, DefaultVisual(xDisplay, 0), boardWidth, boardHeight);
2912 }
2913
2914 void
2915 DrawSeekClose ()
2916 {
2917     cairo_surface_destroy(cs);
2918 }
2919
2920 void
2921 DrawGrid()
2922 {
2923   /* draws a grid starting around Nx, Ny squares starting at x,y */
2924   int i;
2925   cairo_t *cr;
2926
2927   DrawSeekOpen();
2928   /* get a cairo_t */
2929   cr = cairo_create (cs);
2930
2931   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
2932   SetPen(cr, lineGap, "#000000", 0);
2933
2934   /* lines in X */
2935   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
2936     {
2937       cairo_move_to (cr, gridSegments[i].x1, gridSegments[i].y1);
2938       cairo_line_to (cr, gridSegments[i].x2, gridSegments[i].y2);
2939       cairo_stroke (cr);
2940     }
2941
2942   /* free memory */
2943   cairo_destroy (cr);
2944   DrawSeekClose();
2945
2946   return;
2947 }
2948
2949 /*
2950  * event handler for redrawing the board
2951  */
2952 void
2953 DrawPositionProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2954 {
2955     DrawPosition(True, NULL);
2956 }
2957
2958
2959 void
2960 HandlePV (Widget w, XEvent * event, String * params, Cardinal * nParams)
2961 {   // [HGM] pv: walk PV
2962     MovePV(event->xmotion.x, event->xmotion.y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
2963 }
2964
2965 static int savedIndex;  /* gross that this is global */
2966
2967 void
2968 CommentClick (Widget w, XEvent * event, String * params, Cardinal * nParams)
2969 {
2970         String val;
2971         XawTextPosition index, dummy;
2972         Arg arg;
2973
2974         XawTextGetSelectionPos(w, &index, &dummy);
2975         XtSetArg(arg, XtNstring, &val);
2976         XtGetValues(w, &arg, 1);
2977         ReplaceComment(savedIndex, val);
2978         if(savedIndex != currentMove) ToNrEvent(savedIndex);
2979         LoadVariation( index, val ); // [HGM] also does the actual moving to it, now
2980 }
2981
2982 void
2983 EditCommentPopUp (int index, char *title, char *text)
2984 {
2985     savedIndex = index;
2986     if (text == NULL) text = "";
2987     NewCommentPopup(title, text, index);
2988 }
2989
2990 void
2991 CommentPopUp (char *title, char *text)
2992 {
2993     savedIndex = currentMove; // [HGM] vari
2994     NewCommentPopup(title, text, currentMove);
2995 }
2996
2997 void
2998 CommentPopDown ()
2999 {
3000     PopDown(CommentDlg);
3001 }
3002
3003
3004 /* Disable all user input other than deleting the window */
3005 static int frozen = 0;
3006
3007 void
3008 FreezeUI ()
3009 {
3010   if (frozen) return;
3011   /* Grab by a widget that doesn't accept input */
3012   XtAddGrab(optList[W_MESSG].handle, TRUE, FALSE);
3013   frozen = 1;
3014 }
3015
3016 /* Undo a FreezeUI */
3017 void
3018 ThawUI ()
3019 {
3020   if (!frozen) return;
3021   XtRemoveGrab(optList[W_MESSG].handle);
3022   frozen = 0;
3023 }
3024
3025 void
3026 ModeHighlight ()
3027 {
3028     Arg args[16];
3029     static int oldPausing = FALSE;
3030     static GameMode oldmode = (GameMode) -1;
3031     char *wname;
3032
3033     if (!boardWidget || !XtIsRealized(boardWidget)) return;
3034
3035     if (pausing != oldPausing) {
3036         oldPausing = pausing;
3037         MarkMenuItem("Mode.Pause", pausing);
3038
3039         if (appData.showButtonBar) {
3040           /* Always toggle, don't set.  Previous code messes up when
3041              invoked while the button is pressed, as releasing it
3042              toggles the state again. */
3043           {
3044             Pixel oldbg, oldfg;
3045             XtSetArg(args[0], XtNbackground, &oldbg);
3046             XtSetArg(args[1], XtNforeground, &oldfg);
3047             XtGetValues(optList[W_PAUSE].handle,
3048                         args, 2);
3049             XtSetArg(args[0], XtNbackground, oldfg);
3050             XtSetArg(args[1], XtNforeground, oldbg);
3051           }
3052           XtSetValues(optList[W_PAUSE].handle, args, 2);
3053         }
3054     }
3055
3056     wname = ModeToWidgetName(oldmode);
3057     if (wname != NULL) {
3058         MarkMenuItem(wname, False);
3059     }
3060     wname = ModeToWidgetName(gameMode);
3061     if (wname != NULL) {
3062         MarkMenuItem(wname, True);
3063     }
3064     oldmode = gameMode;
3065     MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
3066
3067     /* Maybe all the enables should be handled here, not just this one */
3068     EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
3069
3070     DisplayLogos(optList[W_WHITE-1].handle, optList[W_BLACK+1].handle);
3071 }
3072
3073
3074 /*
3075  * Button/menu procedures
3076  */
3077
3078 /* this variable is shared between CopyPositionProc and SendPositionSelection */
3079 char *selected_fen_position=NULL;
3080
3081 Boolean
3082 SendPositionSelection (Widget w, Atom *selection, Atom *target,
3083                        Atom *type_return, XtPointer *value_return,
3084                        unsigned long *length_return, int *format_return)
3085 {
3086   char *selection_tmp;
3087
3088 //  if (!selected_fen_position) return False; /* should never happen */
3089   if (*target == XA_STRING || *target == XA_UTF8_STRING(xDisplay)){
3090    if (!selected_fen_position) { // since it never happens, we use it for indicating a game is being sent
3091     FILE* f = fopen(gameCopyFilename, "r"); // This code, taken from SendGameSelection, now merges the two
3092     long len;
3093     size_t count;
3094     if (f == NULL) return False;
3095     fseek(f, 0, 2);
3096     len = ftell(f);
3097     rewind(f);
3098     selection_tmp = XtMalloc(len + 1);
3099     count = fread(selection_tmp, 1, len, f);
3100     fclose(f);
3101     if (len != count) {
3102       XtFree(selection_tmp);
3103       return False;
3104     }
3105     selection_tmp[len] = NULLCHAR;
3106    } else {
3107     /* note: since no XtSelectionDoneProc was registered, Xt will
3108      * automatically call XtFree on the value returned.  So have to
3109      * make a copy of it allocated with XtMalloc */
3110     selection_tmp= XtMalloc(strlen(selected_fen_position)+16);
3111     safeStrCpy(selection_tmp, selected_fen_position, strlen(selected_fen_position)+16 );
3112    }
3113
3114     *value_return=selection_tmp;
3115     *length_return=strlen(selection_tmp);
3116     *type_return=*target;
3117     *format_return = 8; /* bits per byte */
3118     return True;
3119   } else if (*target == XA_TARGETS(xDisplay)) {
3120     Atom *targets_tmp = (Atom *) XtMalloc(2 * sizeof(Atom));
3121     targets_tmp[0] = XA_UTF8_STRING(xDisplay);
3122     targets_tmp[1] = XA_STRING;
3123     *value_return = targets_tmp;
3124     *type_return = XA_ATOM;
3125     *length_return = 2;
3126 #if 0
3127     // This code leads to a read of value_return out of bounds on 64-bit systems.
3128     // Other code which I have seen always sets *format_return to 32 independent of
3129     // sizeof(Atom) without adjusting *length_return. For instance see TextConvertSelection()
3130     // at http://cgit.freedesktop.org/xorg/lib/libXaw/tree/src/Text.c -- BJ
3131     *format_return = 8 * sizeof(Atom);
3132     if (*format_return > 32) {
3133       *length_return *= *format_return / 32;
3134       *format_return = 32;
3135     }
3136 #else
3137     *format_return = 32;
3138 #endif
3139     return True;
3140   } else {
3141     return False;
3142   }
3143 }
3144
3145 /* note: when called from menu all parameters are NULL, so no clue what the
3146  * Widget which was clicked on was, or what the click event was
3147  */
3148 void
3149 CopySomething (char *src)
3150 {
3151     selected_fen_position = src;
3152     /*
3153      * Set both PRIMARY (the selection) and CLIPBOARD, since we don't
3154      * have a notion of a position that is selected but not copied.
3155      * See http://www.freedesktop.org/wiki/Specifications/ClipboardsWiki
3156      */
3157     XtOwnSelection(menuBarWidget, XA_PRIMARY,
3158                    CurrentTime,
3159                    SendPositionSelection,
3160                    NULL/* lose_ownership_proc */ ,
3161                    NULL/* transfer_done_proc */);
3162     XtOwnSelection(menuBarWidget, XA_CLIPBOARD(xDisplay),
3163                    CurrentTime,
3164                    SendPositionSelection,
3165                    NULL/* lose_ownership_proc */ ,
3166                    NULL/* transfer_done_proc */);
3167 }
3168
3169 /* function called when the data to Paste is ready */
3170 static void
3171 PastePositionCB (Widget w, XtPointer client_data, Atom *selection,
3172                  Atom *type, XtPointer value, unsigned long *len, int *format)
3173 {
3174   char *fenstr=value;
3175   if (value==NULL || *len==0) return; /* nothing had been selected to copy */
3176   fenstr[*len]='\0'; /* normally this string is terminated, but be safe */
3177   EditPositionPasteFEN(fenstr);
3178   XtFree(value);
3179 }
3180
3181 /* called when Paste Position button is pressed,
3182  * all parameters will be NULL */
3183 void
3184 PastePositionProc ()
3185 {
3186     XtGetSelectionValue(menuBarWidget,
3187       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
3188       /* (XtSelectionCallbackProc) */ PastePositionCB,
3189       NULL, /* client_data passed to PastePositionCB */
3190
3191       /* better to use the time field from the event that triggered the
3192        * call to this function, but that isn't trivial to get
3193        */
3194       CurrentTime
3195     );
3196     return;
3197 }
3198
3199 /* note: when called from menu all parameters are NULL, so no clue what the
3200  * Widget which was clicked on was, or what the click event was
3201  */
3202 /* function called when the data to Paste is ready */
3203 static void
3204 PasteGameCB (Widget w, XtPointer client_data, Atom *selection,
3205              Atom *type, XtPointer value, unsigned long *len, int *format)
3206 {
3207   FILE* f;
3208   if (value == NULL || *len == 0) {
3209     return; /* nothing had been selected to copy */
3210   }
3211   f = fopen(gamePasteFilename, "w");
3212   if (f == NULL) {
3213     DisplayError(_("Can't open temp file"), errno);
3214     return;
3215   }
3216   fwrite(value, 1, *len, f);
3217   fclose(f);
3218   XtFree(value);
3219   LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
3220 }
3221
3222 /* called when Paste Game button is pressed,
3223  * all parameters will be NULL */
3224 void
3225 PasteGameProc ()
3226 {
3227     XtGetSelectionValue(menuBarWidget,
3228       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
3229       /* (XtSelectionCallbackProc) */ PasteGameCB,
3230       NULL, /* client_data passed to PasteGameCB */
3231
3232       /* better to use the time field from the event that triggered the
3233        * call to this function, but that isn't trivial to get
3234        */
3235       CurrentTime
3236     );
3237     return;
3238 }
3239
3240
3241 void
3242 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3243 {
3244     QuitProc();
3245 }
3246
3247 int
3248 ShiftKeys ()
3249 {   // bassic primitive for determining if modifier keys are pressed
3250     long int codes[] = { XK_Meta_L, XK_Meta_R, XK_Control_L, XK_Control_R, XK_Shift_L, XK_Shift_R };
3251     char keys[32];
3252     int i,j,  k=0;
3253     XQueryKeymap(xDisplay,keys);
3254     for(i=0; i<6; i++) {
3255         k <<= 1;
3256         j = XKeysymToKeycode(xDisplay, codes[i]);
3257         k += ( (keys[j>>3]&1<<(j&7)) != 0 );
3258     }
3259     return k;
3260 }
3261
3262 static void
3263 MoveTypeInProc (Widget widget, caddr_t unused, XEvent *event)
3264 {
3265     char buf[10];
3266     KeySym sym;
3267     int n = XLookupString(&(event->xkey), buf, 10, &sym, NULL);
3268     if ( n == 1 && *buf >= 32 // printable
3269          && !(ShiftKeys() & 0x3C) // no Alt, Ctrl
3270         ) BoxAutoPopUp (buf);
3271 }
3272
3273 static void
3274 UpKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3275 {   // [HGM] input: let up-arrow recall previous line from history
3276     IcsKey(1);
3277 }
3278
3279 static void
3280 DownKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3281 {   // [HGM] input: let down-arrow recall next line from history
3282     IcsKey(-1);
3283 }
3284
3285 static void
3286 EnterKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3287 {
3288     IcsKey(0);
3289 }
3290
3291 void
3292 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3293 {
3294         if (!TempBackwardActive) {
3295                 TempBackwardActive = True;
3296                 BackwardEvent();
3297         }
3298 }
3299
3300 void
3301 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3302 {
3303         /* Check to see if triggered by a key release event for a repeating key.
3304          * If so the next queued event will be a key press of the same key at the same time */
3305         if (XEventsQueued(xDisplay, QueuedAfterReading)) {
3306                 XEvent next;
3307                 XPeekEvent(xDisplay, &next);
3308                 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
3309                         next.xkey.keycode == event->xkey.keycode)
3310                                 return;
3311         }
3312     ForwardEvent();
3313         TempBackwardActive = False;
3314 }
3315
3316 void
3317 ManInner (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3318 {   // called as key binding
3319     char buf[MSG_SIZ];
3320     String name;
3321     if (nprms && *nprms > 0)
3322       name = prms[0];
3323     else
3324       name = "xboard";
3325     snprintf(buf, sizeof(buf), "xterm -e man %s &", name);
3326     system(buf);
3327 }
3328
3329 void
3330 ManProc ()
3331 {   // called from menu
3332     ManInner(NULL, NULL, NULL, NULL);
3333 }
3334
3335 void
3336 SetWindowTitle (char *text, char *title, char *icon)
3337 {
3338     Arg args[16];
3339     int i;
3340     if (appData.titleInWindow) {
3341         i = 0;
3342         XtSetArg(args[i], XtNlabel, text);   i++;
3343         XtSetValues(titleWidget, args, i);
3344     }
3345     i = 0;
3346     XtSetArg(args[i], XtNiconName, (XtArgVal) icon);    i++;
3347     XtSetArg(args[i], XtNtitle, (XtArgVal) title);      i++;
3348     XtSetValues(shellWidget, args, i);
3349     XSync(xDisplay, False);
3350 }
3351
3352
3353 static int
3354 NullXErrorCheck (Display *dpy, XErrorEvent *error_event)
3355 {
3356     return 0;
3357 }
3358
3359 void
3360 DisplayIcsInteractionTitle (String message)
3361 {
3362   if (oldICSInteractionTitle == NULL) {
3363     /* Magic to find the old window title, adapted from vim */
3364     char *wina = getenv("WINDOWID");
3365     if (wina != NULL) {
3366       Window win = (Window) atoi(wina);
3367       Window root, parent, *children;
3368       unsigned int nchildren;
3369       int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
3370       for (;;) {
3371         if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
3372         if (!XQueryTree(xDisplay, win, &root, &parent,
3373                         &children, &nchildren)) break;
3374         if (children) XFree((void *)children);
3375         if (parent == root || parent == 0) break;
3376         win = parent;
3377       }
3378       XSetErrorHandler(oldHandler);
3379     }
3380     if (oldICSInteractionTitle == NULL) {
3381       oldICSInteractionTitle = "xterm";
3382     }
3383   }
3384   printf("\033]0;%s\007", message);
3385   fflush(stdout);
3386 }
3387
3388
3389 XtIntervalId delayedEventTimerXID = 0;
3390 DelayedEventCallback delayedEventCallback = 0;
3391
3392 void
3393 FireDelayedEvent ()
3394 {
3395     delayedEventTimerXID = 0;
3396     delayedEventCallback();
3397 }
3398
3399 void
3400 ScheduleDelayedEvent (DelayedEventCallback cb, long millisec)
3401 {
3402     if(delayedEventTimerXID && delayedEventCallback == cb)
3403         // [HGM] alive: replace, rather than add or flush identical event
3404         XtRemoveTimeOut(delayedEventTimerXID);
3405     delayedEventCallback = cb;
3406     delayedEventTimerXID =
3407       XtAppAddTimeOut(appContext, millisec,
3408                       (XtTimerCallbackProc) FireDelayedEvent, (XtPointer) 0);
3409 }
3410
3411 DelayedEventCallback
3412 GetDelayedEvent ()
3413 {
3414   if (delayedEventTimerXID) {
3415     return delayedEventCallback;
3416   } else {
3417     return NULL;
3418   }
3419 }
3420
3421 void
3422 CancelDelayedEvent ()
3423 {
3424   if (delayedEventTimerXID) {
3425     XtRemoveTimeOut(delayedEventTimerXID);
3426     delayedEventTimerXID = 0;
3427   }
3428 }
3429
3430 XtIntervalId loadGameTimerXID = 0;
3431
3432 int
3433 LoadGameTimerRunning ()
3434 {
3435     return loadGameTimerXID != 0;
3436 }
3437
3438 int
3439 StopLoadGameTimer ()
3440 {
3441     if (loadGameTimerXID != 0) {
3442         XtRemoveTimeOut(loadGameTimerXID);
3443         loadGameTimerXID = 0;
3444         return TRUE;
3445     } else {
3446         return FALSE;
3447     }
3448 }
3449
3450 void
3451 LoadGameTimerCallback (XtPointer arg, XtIntervalId *id)
3452 {
3453     loadGameTimerXID = 0;
3454     AutoPlayGameLoop();
3455 }
3456
3457 void
3458 StartLoadGameTimer (long millisec)
3459 {
3460     loadGameTimerXID =
3461       XtAppAddTimeOut(appContext, millisec,
3462                       (XtTimerCallbackProc) LoadGameTimerCallback,
3463                       (XtPointer) 0);
3464 }
3465
3466 XtIntervalId analysisClockXID = 0;
3467
3468 void
3469 AnalysisClockCallback (XtPointer arg, XtIntervalId *id)
3470 {
3471     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
3472          || appData.icsEngineAnalyze) { // [DM]
3473         AnalysisPeriodicEvent(0);
3474         StartAnalysisClock();
3475     }
3476 }
3477
3478 void
3479 StartAnalysisClock ()
3480 {
3481     analysisClockXID =
3482       XtAppAddTimeOut(appContext, 2000,
3483                       (XtTimerCallbackProc) AnalysisClockCallback,
3484                       (XtPointer) 0);
3485 }
3486
3487 XtIntervalId clockTimerXID = 0;
3488
3489 int
3490 ClockTimerRunning ()
3491 {
3492     return clockTimerXID != 0;
3493 }
3494
3495 int
3496 StopClockTimer ()
3497 {
3498     if (clockTimerXID != 0) {
3499         XtRemoveTimeOut(clockTimerXID);
3500         clockTimerXID = 0;
3501         return TRUE;
3502     } else {
3503         return FALSE;
3504     }
3505 }
3506
3507 void
3508 ClockTimerCallback (XtPointer arg, XtIntervalId *id)
3509 {
3510     clockTimerXID = 0;
3511     DecrementClocks();
3512 }
3513
3514 void
3515 StartClockTimer (long millisec)
3516 {
3517     clockTimerXID =
3518       XtAppAddTimeOut(appContext, millisec,
3519                       (XtTimerCallbackProc) ClockTimerCallback,
3520                       (XtPointer) 0);
3521 }
3522
3523 void
3524 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
3525 {
3526     char buf[MSG_SIZ];
3527     Arg args[16];
3528     Widget w = (Widget) opt->handle;
3529
3530     /* check for low time warning */
3531     Pixel foregroundOrWarningColor = timerForegroundPixel;
3532
3533     if (timer > 0 &&
3534         appData.lowTimeWarning &&
3535         (timer / 1000) < appData.icsAlarmTime)
3536       foregroundOrWarningColor = lowTimeWarningColor;
3537
3538     if (appData.clockMode) {
3539       snprintf(buf, MSG_SIZ, "%s:%s%s", color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
3540       XtSetArg(args[0], XtNlabel, buf);
3541     } else {
3542       snprintf(buf, MSG_SIZ, "%s  ", color);
3543       XtSetArg(args[0], XtNlabel, buf);
3544     }
3545
3546     if (highlight) {
3547
3548         XtSetArg(args[1], XtNbackground, foregroundOrWarningColor);
3549         XtSetArg(args[2], XtNforeground, timerBackgroundPixel);
3550     } else {
3551         XtSetArg(args[1], XtNbackground, timerBackgroundPixel);
3552         XtSetArg(args[2], XtNforeground, foregroundOrWarningColor);
3553     }
3554
3555     XtSetValues(w, args, 3);
3556 }
3557
3558 static Pixmap *clockIcons[] = { &wIconPixmap, &bIconPixmap };
3559
3560 void
3561 SetClockIcon (int color)
3562 {
3563     Arg args[16];
3564     Pixmap pm = *clockIcons[color];
3565     if (iconPixmap != pm) {
3566         iconPixmap = pm;
3567         XtSetArg(args[0], XtNiconPixmap, iconPixmap);
3568         XtSetValues(shellWidget, args, 1);
3569     }
3570 }
3571
3572 void
3573 DoInputCallback (caddr_t closure, int *source, XtInputId *xid)
3574 {
3575     InputSource *is = (InputSource *) closure;
3576     int count;
3577     int error;
3578     char *p, *q;
3579
3580     if (is->lineByLine) {
3581         count = read(is->fd, is->unused,
3582                      INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
3583         if (count <= 0) {
3584             (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
3585             return;
3586         }
3587         is->unused += count;
3588         p = is->buf;
3589         while (p < is->unused) {
3590             q = memchr(p, '\n', is->unused - p);
3591             if (q == NULL) break;
3592             q++;
3593             (is->func)(is, is->closure, p, q - p, 0);
3594             p = q;
3595         }
3596         q = is->buf;
3597         while (p < is->unused) {
3598             *q++ = *p++;
3599         }
3600         is->unused = q;
3601     } else {
3602         count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
3603         if (count == -1)
3604           error = errno;
3605         else
3606           error = 0;
3607         (is->func)(is, is->closure, is->buf, count, error);
3608     }
3609 }
3610
3611 InputSourceRef
3612 AddInputSource (ProcRef pr, int lineByLine, InputCallback func, VOIDSTAR closure)
3613 {
3614     InputSource *is;
3615     ChildProc *cp = (ChildProc *) pr;
3616
3617     is = (InputSource *) calloc(1, sizeof(InputSource));
3618     is->lineByLine = lineByLine;
3619     is->func = func;
3620     if (pr == NoProc) {
3621         is->kind = CPReal;
3622         is->fd = fileno(stdin);
3623     } else {
3624         is->kind = cp->kind;
3625         is->fd = cp->fdFrom;
3626     }
3627     if (lineByLine) {
3628         is->unused = is->buf;
3629     }
3630
3631     is->xid = XtAppAddInput(appContext, is->fd,
3632                             (XtPointer) (XtInputReadMask),
3633                             (XtInputCallbackProc) DoInputCallback,
3634                             (XtPointer) is);
3635     is->closure = closure;
3636     return (InputSourceRef) is;
3637 }
3638
3639 void
3640 RemoveInputSource (InputSourceRef isr)
3641 {
3642     InputSource *is = (InputSource *) isr;
3643
3644     if (is->xid == 0) return;
3645     XtRemoveInput(is->xid);
3646     is->xid = 0;
3647 }
3648
3649 /****   Animation code by Hugh Fisher, DCS, ANU. ****/
3650
3651 /*      Masks for XPM pieces. Black and white pieces can have
3652         different shapes, but in the interest of retaining my
3653         sanity pieces must have the same outline on both light
3654         and dark squares, and all pieces must use the same
3655         background square colors/images.                */
3656
3657 static int xpmDone = 0;
3658 static Pixmap animBufs[3*NrOfAnims]; // newBuf, saveBuf
3659 static GC animGCs[3*NrOfAnims]; // blitGC, pieceGC, outlineGC;
3660
3661 static void
3662 CreateAnimMasks (int pieceDepth)
3663 {
3664   ChessSquare   piece;
3665   Pixmap        buf;
3666   GC            bufGC, maskGC;
3667   int           kind, n;
3668   unsigned long plane;
3669   XGCValues     values;
3670
3671   /* Need a bitmap just to get a GC with right depth */
3672   buf = XCreatePixmap(xDisplay, xBoardWindow,
3673                         8, 8, 1);
3674   values.foreground = 1;
3675   values.background = 0;
3676   /* Don't use XtGetGC, not read only */
3677   maskGC = XCreateGC(xDisplay, buf,
3678                     GCForeground | GCBackground, &values);
3679   XFreePixmap(xDisplay, buf);
3680
3681   buf = XCreatePixmap(xDisplay, xBoardWindow,
3682                       squareSize, squareSize, pieceDepth);
3683   values.foreground = XBlackPixel(xDisplay, xScreen);
3684   values.background = XWhitePixel(xDisplay, xScreen);
3685   bufGC = XCreateGC(xDisplay, buf,
3686                     GCForeground | GCBackground, &values);
3687
3688   for (piece = WhitePawn; piece <= BlackKing; piece++) {
3689     /* Begin with empty mask */
3690     if(!xpmDone) // [HGM] pieces: keep using existing
3691     xpmMask[piece] = XCreatePixmap(xDisplay, xBoardWindow,
3692                                  squareSize, squareSize, 1);
3693     XSetFunction(xDisplay, maskGC, GXclear);
3694     XFillRectangle(xDisplay, xpmMask[piece], maskGC,
3695                    0, 0, squareSize, squareSize);
3696
3697     /* Take a copy of the piece */
3698     if (White(piece))
3699       kind = 0;
3700     else
3701       kind = 2;
3702     XSetFunction(xDisplay, bufGC, GXcopy);
3703     XCopyArea(xDisplay, xpmPieceBitmap[kind][((int)piece) % (int)BlackPawn],
3704               buf, bufGC,
3705               0, 0, squareSize, squareSize, 0, 0);
3706
3707     /* XOR the background (light) over the piece */
3708     XSetFunction(xDisplay, bufGC, GXxor);
3709     if (useImageSqs)
3710       XCopyArea(xDisplay, xpmLightSquare, buf, bufGC,
3711                 0, 0, squareSize, squareSize, 0, 0);
3712     else {
3713       XSetForeground(xDisplay, bufGC, lightSquareColor);
3714       XFillRectangle(xDisplay, buf, bufGC, 0, 0, squareSize, squareSize);
3715     }
3716
3717     /* We now have an inverted piece image with the background
3718        erased. Construct mask by just selecting all the non-zero
3719        pixels - no need to reconstruct the original image.      */
3720     XSetFunction(xDisplay, maskGC, GXor);
3721     plane = 1;
3722     /* Might be quicker to download an XImage and create bitmap
3723        data from it rather than this N copies per piece, but it
3724        only takes a fraction of a second and there is a much
3725        longer delay for loading the pieces.             */
3726     for (n = 0; n < pieceDepth; n ++) {
3727       XCopyPlane(xDisplay, buf, xpmMask[piece], maskGC,
3728                  0, 0, squareSize, squareSize,
3729                  0, 0, plane);
3730       plane = plane << 1;
3731     }
3732   }
3733   /* Clean up */
3734   XFreePixmap(xDisplay, buf);
3735   XFreeGC(xDisplay, bufGC);
3736   XFreeGC(xDisplay, maskGC);
3737 }
3738
3739 static void
3740 InitAnimState (AnimNr anr, XWindowAttributes *info)
3741 {
3742   XtGCMask  mask;
3743   XGCValues values;
3744
3745   /* Each buffer is square size, same depth as window */
3746   animBufs[anr+4] = xBoardWindow;
3747   animBufs[anr+2] = XCreatePixmap(xDisplay, xBoardWindow,
3748                         squareSize, squareSize, info->depth);
3749   animBufs[anr] = XCreatePixmap(xDisplay, xBoardWindow,
3750                         squareSize, squareSize, info->depth);
3751
3752   /* Create a plain GC for blitting */
3753   mask = GCForeground | GCBackground | GCFunction |
3754          GCPlaneMask | GCGraphicsExposures;
3755   values.foreground = XBlackPixel(xDisplay, xScreen);
3756   values.background = XWhitePixel(xDisplay, xScreen);
3757   values.function   = GXcopy;
3758   values.plane_mask = AllPlanes;
3759   values.graphics_exposures = False;
3760   animGCs[anr] = XCreateGC(xDisplay, xBoardWindow, mask, &values);
3761
3762   /* Piece will be copied from an existing context at
3763      the start of each new animation/drag. */
3764   animGCs[anr+2] = XCreateGC(xDisplay, xBoardWindow, 0, &values);
3765
3766   /* Outline will be a read-only copy of an existing */
3767   animGCs[anr+4] = None;
3768 }
3769
3770 void
3771 CreateAnimVars ()
3772 {
3773   XWindowAttributes info;
3774
3775   if (xpmDone && gameInfo.variant == oldVariant) return;
3776   if(xpmDone) oldVariant = gameInfo.variant; // first time pieces might not be created yet
3777   XGetWindowAttributes(xDisplay, xBoardWindow, &info);
3778
3779   InitAnimState(Game, &info);
3780   InitAnimState(Player, &info);
3781
3782   /* For XPM pieces, we need bitmaps to use as masks. */
3783   if (useImages)
3784     CreateAnimMasks(info.depth), xpmDone = 1;
3785 }
3786
3787 #ifndef HAVE_USLEEP
3788
3789 static Boolean frameWaiting;
3790
3791 static RETSIGTYPE
3792 FrameAlarm (int sig)
3793 {
3794   frameWaiting = False;
3795   /* In case System-V style signals.  Needed?? */
3796   signal(SIGALRM, FrameAlarm);
3797 }
3798
3799 void
3800 FrameDelay (int time)
3801 {
3802   struct itimerval delay;
3803
3804   XSync(xDisplay, False);
3805
3806   if (time > 0) {
3807     frameWaiting = True;
3808     signal(SIGALRM, FrameAlarm);
3809     delay.it_interval.tv_sec =
3810       delay.it_value.tv_sec = time / 1000;
3811     delay.it_interval.tv_usec =
3812       delay.it_value.tv_usec = (time % 1000) * 1000;
3813     setitimer(ITIMER_REAL, &delay, NULL);
3814     while (frameWaiting) pause();
3815     delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
3816     delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
3817     setitimer(ITIMER_REAL, &delay, NULL);
3818   }
3819 }
3820
3821 #else
3822
3823 void
3824 FrameDelay (int time)
3825 {
3826   XSync(xDisplay, False);
3827   if (time > 0)
3828     usleep(time * 1000);
3829 }
3830
3831 #endif
3832
3833 static void
3834 SelectGCMask (ChessSquare piece, GC *clip, GC *outline, Pixmap *mask)
3835 {
3836   GC source;
3837
3838   /* Bitmap for piece being moved. */
3839   if (appData.monoMode) {
3840       *mask = *pieceToSolid(piece);
3841   } else if (useImages) {
3842 #if HAVE_LIBXPM
3843       *mask = xpmMask[piece];
3844 #else
3845       *mask = ximMaskPm[piece];
3846 #endif
3847   } else {
3848       *mask = *pieceToSolid(piece);
3849   }
3850
3851   /* GC for piece being moved. Square color doesn't matter, but
3852      since it gets modified we make a copy of the original. */
3853   if (White(piece)) {
3854     if (appData.monoMode)
3855       source = bwPieceGC;
3856     else
3857       source = wlPieceGC;
3858   } else {
3859     if (appData.monoMode)
3860       source = wbPieceGC;
3861     else
3862       source = blPieceGC;
3863   }
3864   XCopyGC(xDisplay, source, 0xFFFFFFFF, *clip);
3865
3866   /* Outline only used in mono mode and is not modified */
3867   if (White(piece))
3868     *outline = bwPieceGC;
3869   else
3870     *outline = wbPieceGC;
3871 }
3872
3873 static void
3874 OverlayPiece (ChessSquare piece, GC clip, GC outline,  Drawable dest)
3875 {
3876   int   kind;
3877
3878   if (!useImages) {
3879     /* Draw solid rectangle which will be clipped to shape of piece */
3880     XFillRectangle(xDisplay, dest, clip,
3881                    0, 0, squareSize, squareSize);
3882     if (appData.monoMode)
3883       /* Also draw outline in contrasting color for black
3884          on black / white on white cases                */
3885       XCopyPlane(xDisplay, *pieceToOutline(piece), dest, outline,
3886                  0, 0, squareSize, squareSize, 0, 0, 1);
3887   } else {
3888     /* Copy the piece */
3889     if (White(piece))
3890       kind = 0;
3891     else
3892       kind = 2;
3893     if(appData.upsideDown && flipView) kind ^= 2;
3894     XCopyArea(xDisplay, xpmPieceBitmap[kind][piece],
3895               dest, clip,
3896               0, 0, squareSize, squareSize,
3897               0, 0);
3898   }
3899 }
3900
3901 void
3902 InsertPiece (AnimNr anr, ChessSquare piece)
3903 {
3904   OverlayPiece(piece, animGCs[anr+2], animGCs[anr+4], animBufs[anr]);
3905 }
3906
3907 void
3908 DrawBlank (AnimNr anr, int x, int y, int startColor)
3909 {
3910     BlankSquare(x, y, startColor, EmptySquare, animBufs[anr+2], 0);
3911 }
3912
3913 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
3914                  int srcX, int srcY, int width, int height, int destX, int destY)
3915 {
3916     XCopyArea(xDisplay, animBufs[anr+srcBuf], animBufs[anr+destBuf], animGCs[anr],
3917                 srcX, srcY, width, height, destX, destY);
3918 }
3919
3920 void
3921 SetDragPiece (AnimNr anr, ChessSquare piece)
3922 {
3923   Pixmap mask;
3924   /* The piece will be drawn using its own bitmap as a matte    */
3925   SelectGCMask(piece, &animGCs[anr+2], &animGCs[anr+4], &mask);
3926   XSetClipMask(xDisplay, animGCs[anr+2], mask);
3927 }
3928
3929 /* [AS] Arrow highlighting support */
3930
3931 void DrawPolygon(Pnt arrow[], int nr)
3932 {   // for now on own surface; eventually this should become a global that is only destroyed on resize
3933     cairo_surface_t *boardSurface;
3934     cairo_t *cr;
3935     int i;
3936     int w = lineGap + BOARD_WIDTH * (squareSize + lineGap);
3937     int h = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
3938     boardSurface = cairo_xlib_surface_create(xDisplay, xBoardWindow, DefaultVisual(xDisplay, 0), w, h);
3939     cr = cairo_create (boardSurface);
3940     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
3941     for (i=0;i<nr;i++) {
3942         cairo_line_to(cr, arrow[i].x, arrow[i].y);
3943     }
3944     if(appData.monoMode) { // should we always outline arrow?
3945         cairo_line_to(cr, arrow[0].x, arrow[0].y);
3946         SetPen(cr, 2, "#000000", 0);
3947         cairo_stroke_preserve(cr);
3948     }
3949     SetPen(cr, 2, appData.highlightSquareColor, 0);
3950     cairo_fill(cr);
3951
3952     /* free memory */
3953     cairo_destroy (cr);
3954     cairo_surface_destroy (boardSurface);
3955 }
3956
3957 static void
3958 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
3959 {
3960     char buf[MSG_SIZ], *logoName = buf;
3961     if(appData.logo[n][0]) {
3962         logoName = appData.logo[n];
3963     } else if(appData.autoLogo) {
3964         if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
3965             sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
3966         } else if(appData.directory[n] && appData.directory[n][0]) {
3967             sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
3968         }
3969     }
3970     if(logoName[0])
3971         { ASSIGN(cps->programLogo, logoName); }
3972 }
3973
3974 void
3975 UpdateLogos (int displ)
3976 {
3977     if(optList[W_WHITE-1].handle == NULL) return;
3978     LoadLogo(&first, 0, 0);
3979     LoadLogo(&second, 1, appData.icsActive);
3980     if(displ) DisplayLogos(optList[W_WHITE-1].handle, optList[W_BLACK+1].handle);
3981     return;
3982 }
3983