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