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