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