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