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