Do animation with cairo
[xboard.git] / xboard.c
1 /*
2  * xboard.c -- X front end for XBoard
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * The following terms apply to Digital Equipment Corporation's copyright
11  * interest in XBoard:
12  * ------------------------------------------------------------------------
13  * All Rights Reserved
14  *
15  * Permission to use, copy, modify, and distribute this software and its
16  * documentation for any purpose and without fee is hereby granted,
17  * provided that the above copyright notice appear in all copies and that
18  * both that copyright notice and this permission notice appear in
19  * supporting documentation, and that the name of Digital not be
20  * used in advertising or publicity pertaining to distribution of the
21  * software without specific, written prior permission.
22  *
23  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
24  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
25  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
26  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
27  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
28  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
29  * SOFTWARE.
30  * ------------------------------------------------------------------------
31  *
32  * The following terms apply to the enhanced version of XBoard
33  * distributed by the Free Software Foundation:
34  * ------------------------------------------------------------------------
35  *
36  * GNU XBoard is free software: you can redistribute it and/or modify
37  * it under the terms of the GNU General Public License as published by
38  * the Free Software Foundation, either version 3 of the License, or (at
39  * your option) any later version.
40  *
41  * GNU XBoard is distributed in the hope that it will be useful, but
42  * WITHOUT ANY WARRANTY; without even the implied warranty of
43  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
44  * General Public License for more details.
45  *
46  * You should have received a copy of the GNU General Public License
47  * along with this program. If not, see http://www.gnu.org/licenses/.  *
48  *
49  *------------------------------------------------------------------------
50  ** See the file ChangeLog for a revision history.  */
51
52 #define HIGHDRAG 1
53
54 #include "config.h"
55
56 #include <stdio.h>
57 #include <ctype.h>
58 #include <signal.h>
59 #include <errno.h>
60 #include <sys/types.h>
61 #include <sys/stat.h>
62 #include <pwd.h>
63 #include <math.h>
64 #include <cairo/cairo.h>
65 #include <cairo/cairo-xlib.h>
66
67 #if !OMIT_SOCKETS
68 # if HAVE_SYS_SOCKET_H
69 #  include <sys/socket.h>
70 #  include <netinet/in.h>
71 #  include <netdb.h>
72 # else /* not HAVE_SYS_SOCKET_H */
73 #  if HAVE_LAN_SOCKET_H
74 #   include <lan/socket.h>
75 #   include <lan/in.h>
76 #   include <lan/netdb.h>
77 #  else /* not HAVE_LAN_SOCKET_H */
78 #   define OMIT_SOCKETS 1
79 #  endif /* not HAVE_LAN_SOCKET_H */
80 # endif /* not HAVE_SYS_SOCKET_H */
81 #endif /* !OMIT_SOCKETS */
82
83 #if STDC_HEADERS
84 # include <stdlib.h>
85 # include <string.h>
86 #else /* not STDC_HEADERS */
87 extern char *getenv();
88 # if HAVE_STRING_H
89 #  include <string.h>
90 # else /* not HAVE_STRING_H */
91 #  include <strings.h>
92 # endif /* not HAVE_STRING_H */
93 #endif /* not STDC_HEADERS */
94
95 #if HAVE_SYS_FCNTL_H
96 # include <sys/fcntl.h>
97 #else /* not HAVE_SYS_FCNTL_H */
98 # if HAVE_FCNTL_H
99 #  include <fcntl.h>
100 # endif /* HAVE_FCNTL_H */
101 #endif /* not HAVE_SYS_FCNTL_H */
102
103 #if HAVE_SYS_SYSTEMINFO_H
104 # include <sys/systeminfo.h>
105 #endif /* HAVE_SYS_SYSTEMINFO_H */
106
107 #if TIME_WITH_SYS_TIME
108 # include <sys/time.h>
109 # include <time.h>
110 #else
111 # if HAVE_SYS_TIME_H
112 #  include <sys/time.h>
113 # else
114 #  include <time.h>
115 # endif
116 #endif
117
118 #if HAVE_UNISTD_H
119 # include <unistd.h>
120 #endif
121
122 #if HAVE_SYS_WAIT_H
123 # include <sys/wait.h>
124 #endif
125
126 #if HAVE_DIRENT_H
127 # include <dirent.h>
128 # define NAMLEN(dirent) strlen((dirent)->d_name)
129 # define HAVE_DIR_STRUCT
130 #else
131 # define dirent direct
132 # define NAMLEN(dirent) (dirent)->d_namlen
133 # if HAVE_SYS_NDIR_H
134 #  include <sys/ndir.h>
135 #  define HAVE_DIR_STRUCT
136 # endif
137 # if HAVE_SYS_DIR_H
138 #  include <sys/dir.h>
139 #  define HAVE_DIR_STRUCT
140 # endif
141 # if HAVE_NDIR_H
142 #  include <ndir.h>
143 #  define HAVE_DIR_STRUCT
144 # endif
145 #endif
146
147 #if ENABLE_NLS
148 #include <locale.h>
149 #endif
150
151 #include <X11/Intrinsic.h>
152 #include <X11/StringDefs.h>
153 #include <X11/Shell.h>
154 #include <X11/cursorfont.h>
155 #include <X11/Xatom.h>
156 #include <X11/Xmu/Atoms.h>
157 #if USE_XAW3D
158 #include <X11/Xaw3d/Dialog.h>
159 #include <X11/Xaw3d/Form.h>
160 #include <X11/Xaw3d/List.h>
161 #include <X11/Xaw3d/Label.h>
162 #include <X11/Xaw3d/SimpleMenu.h>
163 #include <X11/Xaw3d/SmeBSB.h>
164 #include <X11/Xaw3d/SmeLine.h>
165 #include <X11/Xaw3d/Box.h>
166 #include <X11/Xaw3d/MenuButton.h>
167 #include <X11/Xaw3d/Text.h>
168 #include <X11/Xaw3d/AsciiText.h>
169 #else
170 #include <X11/Xaw/Dialog.h>
171 #include <X11/Xaw/Form.h>
172 #include <X11/Xaw/List.h>
173 #include <X11/Xaw/Label.h>
174 #include <X11/Xaw/SimpleMenu.h>
175 #include <X11/Xaw/SmeBSB.h>
176 #include <X11/Xaw/SmeLine.h>
177 #include <X11/Xaw/Box.h>
178 #include <X11/Xaw/MenuButton.h>
179 #include <X11/Xaw/Text.h>
180 #include <X11/Xaw/AsciiText.h>
181 #endif
182
183 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
184 #include "common.h"
185
186 #if HAVE_LIBXPM
187 #include <X11/xpm.h>
188 #include "pixmaps/pixmaps.h"
189 #define IMAGE_EXT "xpm"
190 #else
191 #define IMAGE_EXT "xim"
192 #include "bitmaps/bitmaps.h"
193 #endif
194
195 #include "bitmaps/icon_white.bm"
196 #include "bitmaps/icon_black.bm"
197 #include "bitmaps/checkmark.bm"
198
199 #include "frontend.h"
200 #include "backend.h"
201 #include "backendz.h"
202 #include "moves.h"
203 #include "xboard.h"
204 #include "childio.h"
205 #include "xgamelist.h"
206 #include "xhistory.h"
207 #include "xevalgraph.h"
208 #include "xedittags.h"
209 #include "menus.h"
210 #include "board.h"
211 #include "dialogs.h"
212 #include "engineoutput.h"
213 #include "usystem.h"
214 #include "gettext.h"
215
216
217 #ifdef __EMX__
218 #ifndef HAVE_USLEEP
219 #define HAVE_USLEEP
220 #endif
221 #define usleep(t)   _sleep2(((t)+500)/1000)
222 #endif
223
224 #ifdef ENABLE_NLS
225 # define  _(s) gettext (s)
226 # define N_(s) gettext_noop (s)
227 #else
228 # define  _(s) (s)
229 # define N_(s)  s
230 #endif
231
232 int main P((int argc, char **argv));
233 RETSIGTYPE CmailSigHandler P((int sig));
234 RETSIGTYPE IntSigHandler P((int sig));
235 RETSIGTYPE TermSizeSigHandler P((int sig));
236 static void CreateGCs P((int redo));
237 static void CreateAnyPieces P((void));
238 void CreateXIMPieces P((void));
239 void CreateXPMPieces P((void));
240 void CreatePNGPieces P((void));
241 void CreateXPMBoard P((char *s, int n));
242 void CreatePieces P((void));
243 Widget CreateMenuBar P((Menu *mb, int boardWidth));
244 #if ENABLE_NLS
245 char *InsertPxlSize P((char *pattern, int targetPxlSize));
246 XFontSet CreateFontSet P((char *base_fnt_lst));
247 #else
248 char *FindFont P((char *pattern, int targetPxlSize));
249 #endif
250 void ReadBitmap P((Pixmap *pm, String name, unsigned char bits[],
251                    u_int wreq, u_int hreq));
252 void CreateGrid P((void));
253 void EventProc P((Widget widget, caddr_t unused, XEvent *event));
254 void DelayedDrag P((void));
255 static void MoveTypeInProc P((Widget widget, caddr_t unused, XEvent *event));
256 void HandlePV P((Widget w, XEvent * event,
257                      String * params, Cardinal * nParams));
258 void DrawPositionProc P((Widget w, XEvent *event,
259                      String *prms, Cardinal *nprms));
260 void CommentClick P((Widget w, XEvent * event,
261                    String * params, Cardinal * nParams));
262 void ICSInputBoxPopUp P((void));
263 void SelectCommand P((Widget w, XtPointer client_data, XtPointer call_data));
264 void KeyBindingProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
265 void QuitWrapper P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
266 static void EnterKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
267 static void UpKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
268 static void DownKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
269 void TempBackwardProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
270 void TempForwardProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
271 Boolean TempBackwardActive = False;
272 void ManInner P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
273 void DisplayMove P((int moveNumber));
274 void ICSInitScript P((void));
275 void SelectMove P((Widget w, XEvent * event, String * params, Cardinal * nParams));
276 void update_ics_width P(());
277 int CopyMemoProc P(());
278
279 /*
280 * XBoard depends on Xt R4 or higher
281 */
282 int xtVersion = XtSpecificationRelease;
283
284 int xScreen;
285 Display *xDisplay;
286 Window xBoardWindow;
287 Pixel lightSquareColor, darkSquareColor, whitePieceColor, blackPieceColor,
288   highlightSquareColor, premoveHighlightColor, dialogColor, buttonColor;
289 Pixel lowTimeWarningColor;
290 GC lightSquareGC, darkSquareGC, lineGC, wdPieceGC, wlPieceGC,
291   bdPieceGC, blPieceGC, wbPieceGC, bwPieceGC, coordGC, highlineGC,
292   prelineGC, countGC;
293 Pixmap iconPixmap, wIconPixmap, bIconPixmap, xMarkPixmap;
294 Widget shellWidget, formWidget, boardWidget, titleWidget, dropMenu, menuBarWidget;
295 Option *optList; // contains all widgets of main window
296 XSegment gridSegments[BOARD_RANKS + BOARD_FILES + 2];
297 #if ENABLE_NLS
298 XFontSet fontSet, clockFontSet;
299 #else
300 Font clockFontID;
301 XFontStruct *clockFontStruct;
302 #endif
303 Font coordFontID, countFontID;
304 XFontStruct *coordFontStruct, *countFontStruct;
305 XtAppContext appContext;
306 char *layoutName;
307
308 char installDir[] = "."; // [HGM] UCI: needed for UCI; probably needs run-time initializtion
309
310 Position commentX = -1, commentY = -1;
311 Dimension commentW, commentH;
312 typedef unsigned int BoardSize;
313 BoardSize boardSize;
314 Boolean chessProgram;
315
316 int  minX, minY; // [HGM] placement: volatile limits on upper-left corner
317 int smallLayout = 0, tinyLayout = 0,
318   marginW, marginH, // [HGM] for run-time resizing
319   fromX = -1, fromY = -1, toX, toY, commentUp = False,
320   errorExitStatus = -1, defaultLineGap;
321 Dimension textHeight;
322 Pixel timerForegroundPixel, timerBackgroundPixel;
323 Pixel buttonForegroundPixel, buttonBackgroundPixel;
324 char *chessDir, *programName, *programVersion;
325 Boolean alwaysOnTop = False;
326 char *icsTextMenuString;
327 char *icsNames;
328 char *firstChessProgramNames;
329 char *secondChessProgramNames;
330
331 WindowPlacement wpMain;
332 WindowPlacement wpConsole;
333 WindowPlacement wpComment;
334 WindowPlacement wpMoveHistory;
335 WindowPlacement wpEvalGraph;
336 WindowPlacement wpEngineOutput;
337 WindowPlacement wpGameList;
338 WindowPlacement wpTags;
339
340
341 #define SOLID 0
342 #define OUTLINE 1
343 Boolean cairoAnimate = True;
344 static cairo_surface_t *csBoardWindow;
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 != 256 || h != 256) { 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/256., squareSize/256.);
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 DrawBorder (int x, int y, int type)
2501 {
2502     cairo_t *cr;
2503     DrawSeekOpen();
2504
2505     cr = cairo_create(csBoardWindow);
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 static int
2513 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
2514 {
2515     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
2516     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
2517     *x0 = 0; *y0 = 0;
2518     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
2519     if(textureW[kind] < W*squareSize)
2520         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
2521     else
2522         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
2523     if(textureH[kind] < H*squareSize)
2524         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
2525     else
2526         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
2527     return 1;
2528 }
2529
2530 void
2531 DrawLogo (void *handle, void *logo)
2532 {
2533     cairo_surface_t *img, *cs;
2534     cairo_t *cr;
2535     int w, h;
2536
2537     if(!logo || !handle) return;
2538     cs = cairo_xlib_surface_create(xDisplay, XtWindow(handle), DefaultVisual(xDisplay, 0), appData.logoSize, appData.logoSize/2);
2539     img = cairo_image_surface_create_from_png (logo);
2540     w = cairo_image_surface_get_width (img);
2541     h = cairo_image_surface_get_height (img);
2542     cr = cairo_create(cs);
2543     cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
2544     cairo_set_source_surface (cr, img, 0, 0);
2545     cairo_paint (cr);
2546     cairo_destroy (cr);
2547     cairo_surface_destroy (img);
2548     cairo_surface_destroy (cs);
2549 }
2550
2551 static void
2552 BlankSquare (int x, int y, int color, ChessSquare piece, Drawable dest, int fac)
2553 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
2554     int x0, y0;
2555     if (useImages && color != 2 && (useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
2556         if(pngBoardBitmap[color]) {
2557             cairo_t *cr;
2558             if(!fac && !cairoAnimate) return;
2559             DrawSeekOpen();
2560             cr = cairo_create (fac ? csBoardWindow : (cairo_surface_t *) dest);
2561             cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
2562             cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
2563             cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
2564             cairo_fill (cr);
2565             cairo_destroy (cr);
2566         } else
2567         XCopyArea(xDisplay, xpmBoardBitmap[color], dest, wlPieceGC, x0, y0,
2568                   squareSize, squareSize, x*fac, y*fac);
2569     } else
2570     if (useImages && useImageSqs) {
2571         Pixmap pm;
2572         switch (color) {
2573           case 1: /* light */
2574             pm = xpmLightSquare;
2575             break;
2576           case 0: /* dark */
2577             pm = xpmDarkSquare;
2578             break;
2579           case 2: /* neutral */
2580           default:
2581             pm = xpmJailSquare; // [HGM] this is wrong, but apparently never used?
2582             break;
2583         }
2584         XCopyArea(xDisplay, pm, dest, wlPieceGC, 0, 0,
2585                   squareSize, squareSize, x*fac, y*fac);
2586     } else {
2587         GC gc;
2588         switch (color) {
2589           case 1: /* light */
2590             gc = lightSquareGC;
2591             break;
2592           case 0: /* dark */
2593             gc = darkSquareGC;
2594             break;
2595           case 2: /* neutral */
2596           default:
2597             gc = lineGC;
2598             break;
2599         }
2600         XFillRectangle(xDisplay, dest, gc, x*fac, y*fac, squareSize, squareSize);
2601     }
2602 }
2603
2604 /*
2605    I split out the routines to draw a piece so that I could
2606    make a generic flash routine.
2607 */
2608 static void
2609 monoDrawPiece_1bit (ChessSquare piece, int square_color, int x, int y, Drawable dest)
2610 {
2611     /* Avoid XCopyPlane on 1-bit screens to work around Sun bug */
2612     switch (square_color) {
2613       case 1: /* light */
2614       case 2: /* neutral */
2615       default:
2616         XCopyArea(xDisplay, (int) piece < (int) BlackPawn
2617                   ? *pieceToOutline(piece)
2618                   : *pieceToSolid(piece),
2619                   dest, bwPieceGC, 0, 0,
2620                   squareSize, squareSize, x, y);
2621         break;
2622       case 0: /* dark */
2623         XCopyArea(xDisplay, (int) piece < (int) BlackPawn
2624                   ? *pieceToSolid(piece)
2625                   : *pieceToOutline(piece),
2626                   dest, wbPieceGC, 0, 0,
2627                   squareSize, squareSize, x, y);
2628         break;
2629     }
2630 }
2631
2632 static void
2633 monoDrawPiece (ChessSquare piece, int square_color, int x, int y, Drawable dest)
2634 {
2635     switch (square_color) {
2636       case 1: /* light */
2637       case 2: /* neutral */
2638       default:
2639         XCopyPlane(xDisplay, (int) piece < (int) BlackPawn
2640                    ? *pieceToOutline(piece)
2641                    : *pieceToSolid(piece),
2642                    dest, bwPieceGC, 0, 0,
2643                    squareSize, squareSize, x, y, 1);
2644         break;
2645       case 0: /* dark */
2646         XCopyPlane(xDisplay, (int) piece < (int) BlackPawn
2647                    ? *pieceToSolid(piece)
2648                    : *pieceToOutline(piece),
2649                    dest, wbPieceGC, 0, 0,
2650                    squareSize, squareSize, x, y, 1);
2651         break;
2652     }
2653 }
2654
2655 static void
2656 colorDrawPiece (ChessSquare piece, int square_color, int x, int y, Drawable dest)
2657 {
2658     if(pieceToSolid(piece) == NULL) return; // [HGM] bitmaps: make it non-fatal if we have no bitmap;
2659     switch (square_color) {
2660       case 1: /* light */
2661         XCopyPlane(xDisplay, *pieceToSolid(piece),
2662                    dest, (int) piece < (int) BlackPawn
2663                    ? wlPieceGC : blPieceGC, 0, 0,
2664                    squareSize, squareSize, x, y, 1);
2665         break;
2666       case 0: /* dark */
2667         XCopyPlane(xDisplay, *pieceToSolid(piece),
2668                    dest, (int) piece < (int) BlackPawn
2669                    ? wdPieceGC : bdPieceGC, 0, 0,
2670                    squareSize, squareSize, x, y, 1);
2671         break;
2672       case 2: /* neutral */
2673       default:
2674         break; // should never contain pieces
2675     }
2676 }
2677
2678 static void
2679 colorDrawPieceImage (ChessSquare piece, int square_color, int x, int y, Drawable dest)
2680 {
2681     int kind, p = piece;
2682
2683     switch (square_color) {
2684       case 1: /* light */
2685       case 2: /* neutral */
2686       default:
2687         if ((int)piece < (int) BlackPawn) {
2688             kind = 0;
2689         } else {
2690             kind = 2;
2691             piece -= BlackPawn;
2692         }
2693         break;
2694       case 0: /* dark */
2695         if ((int)piece < (int) BlackPawn) {
2696             kind = 1;
2697         } else {
2698             kind = 3;
2699             piece -= BlackPawn;
2700         }
2701         break;
2702     }
2703     if(appData.upsideDown && flipView) { kind ^= 2; p += p < BlackPawn ? BlackPawn : -BlackPawn; }// swap white and black pieces
2704     if(useTexture & square_color+1) {
2705         BlankSquare(x, y, square_color, piece, dest, 1); // erase previous contents with background
2706         XSetClipMask(xDisplay, wlPieceGC, xpmMask[p]);
2707         XSetClipOrigin(xDisplay, wlPieceGC, x, y);
2708         XCopyArea(xDisplay, xpmPieceBitmap[kind][piece], dest, wlPieceGC, 0, 0, squareSize, squareSize, x, y);
2709         XSetClipMask(xDisplay, wlPieceGC, None);
2710         XSetClipOrigin(xDisplay, wlPieceGC, 0, 0);
2711     } else
2712     XCopyArea(xDisplay, xpmPieceBitmap[kind][piece],
2713               dest, wlPieceGC, 0, 0,
2714               squareSize, squareSize, x, y);
2715 }
2716
2717 static void
2718 pngDrawPiece (ChessSquare piece, int square_color, int x, int y, Drawable dest)
2719 {
2720     int kind, p = piece;
2721     cairo_t *cr;
2722
2723     if ((int)piece < (int) BlackPawn) {
2724         kind = 0;
2725     } else {
2726         kind = 1;
2727         piece -= BlackPawn;
2728     }
2729     if(appData.upsideDown && flipView) { p += p < BlackPawn ? BlackPawn : -BlackPawn; }// swap white and black pieces
2730     BlankSquare(x, y, square_color, piece, dest, 1); // erase previous contents with background
2731     DrawSeekOpen();
2732     cr = cairo_create (csBoardWindow);
2733     cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
2734     cairo_paint(cr);
2735     cairo_destroy (cr);
2736 }
2737
2738 typedef void (*DrawFunc)();
2739
2740 DrawFunc
2741 ChooseDrawFunc ()
2742 {
2743     if (appData.monoMode) {
2744         if (DefaultDepth(xDisplay, xScreen) == 1) {
2745             return monoDrawPiece_1bit;
2746         } else {
2747             return monoDrawPiece;
2748         }
2749     } else if(appData.pngDirectory[0]) {
2750         return pngDrawPiece;
2751     } else {
2752         if (useImages)
2753           return colorDrawPieceImage;
2754         else
2755           return colorDrawPiece;
2756     }
2757 }
2758
2759 void
2760 DrawDot (int marker, int x, int y, int r)
2761 {
2762         cairo_t *cr;
2763         DrawSeekOpen();
2764         cr = cairo_create(csBoardWindow);
2765         cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
2766         if(appData.monoMode) {
2767             SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
2768             cairo_stroke_preserve(cr);
2769             SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
2770         } else {
2771             SetPen(cr, 2, marker == 2 ? "#FF0000" : "#FFFF00", 0);
2772         }
2773         cairo_fill(cr);
2774         cairo_stroke(cr);
2775
2776         cairo_destroy(cr);
2777 }
2778
2779 void
2780 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *string, int align)
2781 {   // basic front-end board-draw function: takes care of everything that can be in square:
2782     // piece, background, coordinate/count, marker dot
2783     int direction, font_ascent, font_descent;
2784     XCharStruct overall;
2785     DrawFunc drawfunc;
2786
2787     if (piece == EmptySquare) {
2788         BlankSquare(x, y, square_color, piece, xBoardWindow, 1);
2789     } else {
2790         drawfunc = ChooseDrawFunc();
2791         drawfunc(piece, square_color, x, y, xBoardWindow);
2792     }
2793
2794     if(align) { // square carries inscription (coord or piece count)
2795         int xx = x, yy = y;
2796         GC hGC = align < 3 ? coordGC : countGC;
2797         // first calculate where it goes
2798         XTextExtents(countFontStruct, string, 1, &direction,
2799                          &font_ascent, &font_descent, &overall);
2800         if (align == 1) {
2801             xx += squareSize - overall.width - 2;
2802             yy += squareSize - font_descent - 1;
2803         } else if (align == 2) {
2804             xx += 2, yy += font_ascent + 1;
2805         } else if (align == 3) {
2806             xx += squareSize - overall.width - 2;
2807             yy += font_ascent + 1;
2808         } else if (align == 4) {
2809             xx += 2, yy += font_ascent + 1;
2810         }
2811         // then draw it
2812         if (appData.monoMode) {
2813             XDrawImageString(xDisplay, xBoardWindow, hGC, xx, yy, string, 1);
2814         } else {
2815             XDrawString(xDisplay, xBoardWindow, hGC, xx, yy, string, 1);
2816         }
2817     }
2818
2819     if(marker) { // print fat marker dot, if requested
2820         DrawDot(marker, x + squareSize/4, y+squareSize/4, squareSize/2);
2821     }
2822 }
2823
2824 void
2825 FlashDelay (int flash_delay)
2826 {
2827         XSync(xDisplay, False);
2828         if(flash_delay) do_flash_delay(flash_delay);
2829 }
2830
2831 double
2832 Fraction (int x, int start, int stop)
2833 {
2834    double f = ((double) x - start)/(stop - start);
2835    if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
2836    return f;
2837 }
2838
2839 static WindowPlacement wpNew;
2840
2841 void
2842 CoDrag (Widget sh, WindowPlacement *wp)
2843 {
2844     Arg args[16];
2845     int j=0, touch=0, fudge = 2;
2846     GetActualPlacement(sh, wp);
2847     if(abs(wpMain.x + wpMain.width + 2*frameX - wp->x)         < fudge) touch = 1; else // right touch
2848     if(abs(wp->x + wp->width + 2*frameX - wpMain.x)            < fudge) touch = 2; else // left touch
2849     if(abs(wpMain.y + wpMain.height + frameX + frameY - wp->y) < fudge) touch = 3; else // bottom touch
2850     if(abs(wp->y + wp->height + frameX + frameY - wpMain.y)    < fudge) touch = 4;      // top touch
2851     if(!touch ) return; // only windows that touch co-move
2852     if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
2853         int heightInc = wpNew.height - wpMain.height;
2854         double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
2855         double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
2856         wp->y += fracTop * heightInc;
2857         heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
2858         if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
2859     } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
2860         int widthInc = wpNew.width - wpMain.width;
2861         double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
2862         double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
2863         wp->y += fracLeft * widthInc;
2864         widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
2865         if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
2866     }
2867     wp->x += wpNew.x - wpMain.x;
2868     wp->y += wpNew.y - wpMain.y;
2869     if(touch == 1) wp->x += wpNew.width - wpMain.width; else
2870     if(touch == 3) wp->y += wpNew.height - wpMain.height;
2871     XtSetArg(args[j], XtNx, wp->x); j++;
2872     XtSetArg(args[j], XtNy, wp->y); j++;
2873     XtSetValues(sh, args, j);
2874 }
2875
2876 static XtIntervalId delayedDragID = 0;
2877
2878 void
2879 DragProc ()
2880 {
2881         GetActualPlacement(shellWidget, &wpNew);
2882         if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
2883            wpNew.width == wpMain.width && wpNew.height == wpMain.height) // not sized
2884             return; // false alarm
2885         if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
2886         if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
2887         if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
2888         if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
2889         wpMain = wpNew;
2890         DrawPosition(True, NULL);
2891         delayedDragID = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
2892 }
2893
2894
2895 void
2896 DelayedDrag ()
2897 {
2898     if(delayedDragID) XtRemoveTimeOut(delayedDragID); // cancel pending
2899     delayedDragID =
2900       XtAppAddTimeOut(appContext, 50, (XtTimerCallbackProc) DragProc, (XtPointer) 0); // and schedule new one 50 msec later
2901 }
2902
2903 void
2904 EventProc (Widget widget, caddr_t unused, XEvent *event)
2905 {
2906     if(XtIsRealized(widget) && event->type == ConfigureNotify || appData.useStickyWindows)
2907         DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
2908 }
2909
2910 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
2911
2912 float
2913 Color (char *col, int n)
2914 {
2915   int c;
2916   sscanf(col, "#%x", &c);
2917   c = c >> 4*n & 255;
2918   return c/255.;
2919 }
2920
2921 void
2922 SetPen (cairo_t *cr, float w, char *col, int dash)
2923 {
2924   static const double dotted[] = {4.0, 4.0};
2925   static int len  = sizeof(dotted) / sizeof(dotted[0]);
2926   cairo_set_line_width (cr, w);
2927   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
2928   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
2929 }
2930
2931 void DrawSeekAxis( int x, int y, int xTo, int yTo )
2932 {
2933     cairo_t *cr;
2934
2935     /* get a cairo_t */
2936     cr = cairo_create (csBoardWindow);
2937
2938     cairo_move_to (cr, x, y);
2939     cairo_line_to(cr, xTo, yTo );
2940
2941     SetPen(cr, 2, "#000000", 0);
2942     cairo_stroke(cr);
2943
2944     /* free memory */
2945     cairo_destroy (cr);
2946 }
2947
2948 void DrawSeekBackground( int left, int top, int right, int bottom )
2949 {
2950     cairo_t *cr = cairo_create (csBoardWindow);
2951
2952     cairo_rectangle (cr, left, top, right-left, bottom-top);
2953
2954     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
2955     cairo_fill(cr);
2956
2957     /* free memory */
2958     cairo_destroy (cr);
2959 }
2960
2961 void DrawSeekText(char *buf, int x, int y)
2962 {
2963     cairo_t *cr = cairo_create (csBoardWindow);
2964
2965     cairo_select_font_face (cr, "Sans",
2966                             CAIRO_FONT_SLANT_NORMAL,
2967                             CAIRO_FONT_WEIGHT_NORMAL);
2968
2969     cairo_set_font_size (cr, 12.0);
2970
2971     cairo_move_to (cr, x, y+4);
2972     cairo_show_text( cr, buf);
2973
2974     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
2975     cairo_stroke(cr);
2976
2977     /* free memory */
2978     cairo_destroy (cr);
2979 }
2980
2981 void DrawSeekDot(int x, int y, int colorNr)
2982 {
2983     cairo_t *cr = cairo_create (csBoardWindow);
2984     int square = colorNr & 0x80;
2985     colorNr &= 0x7F;
2986
2987     if(square)
2988         cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*squareSize/9, 2*squareSize/9);
2989     else
2990         cairo_arc(cr, x, y, squareSize/8, 0.0, 2*M_PI);
2991
2992     SetPen(cr, 2, "#000000", 0);
2993     cairo_stroke_preserve(cr);
2994     switch (colorNr) {
2995       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
2996       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
2997       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
2998     }
2999     cairo_fill(cr);
3000
3001     /* free memory */
3002     cairo_destroy (cr);
3003 }
3004
3005 void
3006 DrawSeekOpen ()
3007 {
3008     int boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
3009     int boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
3010     if(!csBoardWindow) csBoardWindow = cairo_xlib_surface_create(xDisplay, xBoardWindow, DefaultVisual(xDisplay, 0), boardWidth, boardHeight);
3011 }
3012
3013 void
3014 DrawSeekClose ()
3015 {
3016 }
3017
3018 void
3019 DrawGrid()
3020 {
3021   /* draws a grid starting around Nx, Ny squares starting at x,y */
3022   int i;
3023   cairo_t *cr;
3024
3025   DrawSeekOpen();
3026   /* get a cairo_t */
3027   cr = cairo_create (csBoardWindow);
3028
3029   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
3030   SetPen(cr, lineGap, "#000000", 0);
3031
3032   /* lines in X */
3033   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
3034     {
3035       cairo_move_to (cr, gridSegments[i].x1, gridSegments[i].y1);
3036       cairo_line_to (cr, gridSegments[i].x2, gridSegments[i].y2);
3037       cairo_stroke (cr);
3038     }
3039
3040   /* free memory */
3041   cairo_destroy (cr);
3042
3043   return;
3044 }
3045
3046 /*
3047  * event handler for redrawing the board
3048  */
3049 void
3050 DrawPositionProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3051 {
3052     DrawPosition(True, NULL);
3053 }
3054
3055
3056 void
3057 HandlePV (Widget w, XEvent * event, String * params, Cardinal * nParams)
3058 {   // [HGM] pv: walk PV
3059     MovePV(event->xmotion.x, event->xmotion.y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
3060 }
3061
3062 static int savedIndex;  /* gross that this is global */
3063
3064 void
3065 CommentClick (Widget w, XEvent * event, String * params, Cardinal * nParams)
3066 {
3067         String val;
3068         XawTextPosition index, dummy;
3069         Arg arg;
3070
3071         XawTextGetSelectionPos(w, &index, &dummy);
3072         XtSetArg(arg, XtNstring, &val);
3073         XtGetValues(w, &arg, 1);
3074         ReplaceComment(savedIndex, val);
3075         if(savedIndex != currentMove) ToNrEvent(savedIndex);
3076         LoadVariation( index, val ); // [HGM] also does the actual moving to it, now
3077 }
3078
3079 void
3080 EditCommentPopUp (int index, char *title, char *text)
3081 {
3082     savedIndex = index;
3083     if (text == NULL) text = "";
3084     NewCommentPopup(title, text, index);
3085 }
3086
3087 void
3088 CommentPopUp (char *title, char *text)
3089 {
3090     savedIndex = currentMove; // [HGM] vari
3091     NewCommentPopup(title, text, currentMove);
3092 }
3093
3094 void
3095 CommentPopDown ()
3096 {
3097     PopDown(CommentDlg);
3098 }
3099
3100
3101 /* Disable all user input other than deleting the window */
3102 static int frozen = 0;
3103
3104 void
3105 FreezeUI ()
3106 {
3107   if (frozen) return;
3108   /* Grab by a widget that doesn't accept input */
3109   XtAddGrab(optList[W_MESSG].handle, TRUE, FALSE);
3110   frozen = 1;
3111 }
3112
3113 /* Undo a FreezeUI */
3114 void
3115 ThawUI ()
3116 {
3117   if (!frozen) return;
3118   XtRemoveGrab(optList[W_MESSG].handle);
3119   frozen = 0;
3120 }
3121
3122 void
3123 ModeHighlight ()
3124 {
3125     Arg args[16];
3126     static int oldPausing = FALSE;
3127     static GameMode oldmode = (GameMode) -1;
3128     char *wname;
3129
3130     if (!boardWidget || !XtIsRealized(boardWidget)) return;
3131
3132     if (pausing != oldPausing) {
3133         oldPausing = pausing;
3134         MarkMenuItem("Mode.Pause", pausing);
3135
3136         if (appData.showButtonBar) {
3137           /* Always toggle, don't set.  Previous code messes up when
3138              invoked while the button is pressed, as releasing it
3139              toggles the state again. */
3140           {
3141             Pixel oldbg, oldfg;
3142             XtSetArg(args[0], XtNbackground, &oldbg);
3143             XtSetArg(args[1], XtNforeground, &oldfg);
3144             XtGetValues(optList[W_PAUSE].handle,
3145                         args, 2);
3146             XtSetArg(args[0], XtNbackground, oldfg);
3147             XtSetArg(args[1], XtNforeground, oldbg);
3148           }
3149           XtSetValues(optList[W_PAUSE].handle, args, 2);
3150         }
3151     }
3152
3153     wname = ModeToWidgetName(oldmode);
3154     if (wname != NULL) {
3155         MarkMenuItem(wname, False);
3156     }
3157     wname = ModeToWidgetName(gameMode);
3158     if (wname != NULL) {
3159         MarkMenuItem(wname, True);
3160     }
3161     oldmode = gameMode;
3162     MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
3163
3164     /* Maybe all the enables should be handled here, not just this one */
3165     EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
3166
3167     DisplayLogos(optList[W_WHITE-1].handle, optList[W_BLACK+1].handle);
3168 }
3169
3170
3171 /*
3172  * Button/menu procedures
3173  */
3174
3175 /* this variable is shared between CopyPositionProc and SendPositionSelection */
3176 char *selected_fen_position=NULL;
3177
3178 Boolean
3179 SendPositionSelection (Widget w, Atom *selection, Atom *target,
3180                        Atom *type_return, XtPointer *value_return,
3181                        unsigned long *length_return, int *format_return)
3182 {
3183   char *selection_tmp;
3184
3185 //  if (!selected_fen_position) return False; /* should never happen */
3186   if (*target == XA_STRING || *target == XA_UTF8_STRING(xDisplay)){
3187    if (!selected_fen_position) { // since it never happens, we use it for indicating a game is being sent
3188     FILE* f = fopen(gameCopyFilename, "r"); // This code, taken from SendGameSelection, now merges the two
3189     long len;
3190     size_t count;
3191     if (f == NULL) return False;
3192     fseek(f, 0, 2);
3193     len = ftell(f);
3194     rewind(f);
3195     selection_tmp = XtMalloc(len + 1);
3196     count = fread(selection_tmp, 1, len, f);
3197     fclose(f);
3198     if (len != count) {
3199       XtFree(selection_tmp);
3200       return False;
3201     }
3202     selection_tmp[len] = NULLCHAR;
3203    } else {
3204     /* note: since no XtSelectionDoneProc was registered, Xt will
3205      * automatically call XtFree on the value returned.  So have to
3206      * make a copy of it allocated with XtMalloc */
3207     selection_tmp= XtMalloc(strlen(selected_fen_position)+16);
3208     safeStrCpy(selection_tmp, selected_fen_position, strlen(selected_fen_position)+16 );
3209    }
3210
3211     *value_return=selection_tmp;
3212     *length_return=strlen(selection_tmp);
3213     *type_return=*target;
3214     *format_return = 8; /* bits per byte */
3215     return True;
3216   } else if (*target == XA_TARGETS(xDisplay)) {
3217     Atom *targets_tmp = (Atom *) XtMalloc(2 * sizeof(Atom));
3218     targets_tmp[0] = XA_UTF8_STRING(xDisplay);
3219     targets_tmp[1] = XA_STRING;
3220     *value_return = targets_tmp;
3221     *type_return = XA_ATOM;
3222     *length_return = 2;
3223 #if 0
3224     // This code leads to a read of value_return out of bounds on 64-bit systems.
3225     // Other code which I have seen always sets *format_return to 32 independent of
3226     // sizeof(Atom) without adjusting *length_return. For instance see TextConvertSelection()
3227     // at http://cgit.freedesktop.org/xorg/lib/libXaw/tree/src/Text.c -- BJ
3228     *format_return = 8 * sizeof(Atom);
3229     if (*format_return > 32) {
3230       *length_return *= *format_return / 32;
3231       *format_return = 32;
3232     }
3233 #else
3234     *format_return = 32;
3235 #endif
3236     return True;
3237   } else {
3238     return False;
3239   }
3240 }
3241
3242 /* note: when called from menu all parameters are NULL, so no clue what the
3243  * Widget which was clicked on was, or what the click event was
3244  */
3245 void
3246 CopySomething (char *src)
3247 {
3248     selected_fen_position = src;
3249     /*
3250      * Set both PRIMARY (the selection) and CLIPBOARD, since we don't
3251      * have a notion of a position that is selected but not copied.
3252      * See http://www.freedesktop.org/wiki/Specifications/ClipboardsWiki
3253      */
3254     XtOwnSelection(menuBarWidget, XA_PRIMARY,
3255                    CurrentTime,
3256                    SendPositionSelection,
3257                    NULL/* lose_ownership_proc */ ,
3258                    NULL/* transfer_done_proc */);
3259     XtOwnSelection(menuBarWidget, XA_CLIPBOARD(xDisplay),
3260                    CurrentTime,
3261                    SendPositionSelection,
3262                    NULL/* lose_ownership_proc */ ,
3263                    NULL/* transfer_done_proc */);
3264 }
3265
3266 /* function called when the data to Paste is ready */
3267 static void
3268 PastePositionCB (Widget w, XtPointer client_data, Atom *selection,
3269                  Atom *type, XtPointer value, unsigned long *len, int *format)
3270 {
3271   char *fenstr=value;
3272   if (value==NULL || *len==0) return; /* nothing had been selected to copy */
3273   fenstr[*len]='\0'; /* normally this string is terminated, but be safe */
3274   EditPositionPasteFEN(fenstr);
3275   XtFree(value);
3276 }
3277
3278 /* called when Paste Position button is pressed,
3279  * all parameters will be NULL */
3280 void
3281 PastePositionProc ()
3282 {
3283     XtGetSelectionValue(menuBarWidget,
3284       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
3285       /* (XtSelectionCallbackProc) */ PastePositionCB,
3286       NULL, /* client_data passed to PastePositionCB */
3287
3288       /* better to use the time field from the event that triggered the
3289        * call to this function, but that isn't trivial to get
3290        */
3291       CurrentTime
3292     );
3293     return;
3294 }
3295
3296 /* note: when called from menu all parameters are NULL, so no clue what the
3297  * Widget which was clicked on was, or what the click event was
3298  */
3299 /* function called when the data to Paste is ready */
3300 static void
3301 PasteGameCB (Widget w, XtPointer client_data, Atom *selection,
3302              Atom *type, XtPointer value, unsigned long *len, int *format)
3303 {
3304   FILE* f;
3305   if (value == NULL || *len == 0) {
3306     return; /* nothing had been selected to copy */
3307   }
3308   f = fopen(gamePasteFilename, "w");
3309   if (f == NULL) {
3310     DisplayError(_("Can't open temp file"), errno);
3311     return;
3312   }
3313   fwrite(value, 1, *len, f);
3314   fclose(f);
3315   XtFree(value);
3316   LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
3317 }
3318
3319 /* called when Paste Game button is pressed,
3320  * all parameters will be NULL */
3321 void
3322 PasteGameProc ()
3323 {
3324     XtGetSelectionValue(menuBarWidget,
3325       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
3326       /* (XtSelectionCallbackProc) */ PasteGameCB,
3327       NULL, /* client_data passed to PasteGameCB */
3328
3329       /* better to use the time field from the event that triggered the
3330        * call to this function, but that isn't trivial to get
3331        */
3332       CurrentTime
3333     );
3334     return;
3335 }
3336
3337
3338 void
3339 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3340 {
3341     QuitProc();
3342 }
3343
3344 int
3345 ShiftKeys ()
3346 {   // bassic primitive for determining if modifier keys are pressed
3347     long int codes[] = { XK_Meta_L, XK_Meta_R, XK_Control_L, XK_Control_R, XK_Shift_L, XK_Shift_R };
3348     char keys[32];
3349     int i,j,  k=0;
3350     XQueryKeymap(xDisplay,keys);
3351     for(i=0; i<6; i++) {
3352         k <<= 1;
3353         j = XKeysymToKeycode(xDisplay, codes[i]);
3354         k += ( (keys[j>>3]&1<<(j&7)) != 0 );
3355     }
3356     return k;
3357 }
3358
3359 static void
3360 MoveTypeInProc (Widget widget, caddr_t unused, XEvent *event)
3361 {
3362     char buf[10];
3363     KeySym sym;
3364     int n = XLookupString(&(event->xkey), buf, 10, &sym, NULL);
3365     if ( n == 1 && *buf >= 32 // printable
3366          && !(ShiftKeys() & 0x3C) // no Alt, Ctrl
3367         ) BoxAutoPopUp (buf);
3368 }
3369
3370 static void
3371 UpKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3372 {   // [HGM] input: let up-arrow recall previous line from history
3373     IcsKey(1);
3374 }
3375
3376 static void
3377 DownKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3378 {   // [HGM] input: let down-arrow recall next line from history
3379     IcsKey(-1);
3380 }
3381
3382 static void
3383 EnterKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3384 {
3385     IcsKey(0);
3386 }
3387
3388 void
3389 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3390 {
3391         if (!TempBackwardActive) {
3392                 TempBackwardActive = True;
3393                 BackwardEvent();
3394         }
3395 }
3396
3397 void
3398 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3399 {
3400         /* Check to see if triggered by a key release event for a repeating key.
3401          * If so the next queued event will be a key press of the same key at the same time */
3402         if (XEventsQueued(xDisplay, QueuedAfterReading)) {
3403                 XEvent next;
3404                 XPeekEvent(xDisplay, &next);
3405                 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
3406                         next.xkey.keycode == event->xkey.keycode)
3407                                 return;
3408         }
3409     ForwardEvent();
3410         TempBackwardActive = False;
3411 }
3412
3413 void
3414 ManInner (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3415 {   // called as key binding
3416     char buf[MSG_SIZ];
3417     String name;
3418     if (nprms && *nprms > 0)
3419       name = prms[0];
3420     else
3421       name = "xboard";
3422     snprintf(buf, sizeof(buf), "xterm -e man %s &", name);
3423     system(buf);
3424 }
3425
3426 void
3427 ManProc ()
3428 {   // called from menu
3429     ManInner(NULL, NULL, NULL, NULL);
3430 }
3431
3432 void
3433 SetWindowTitle (char *text, char *title, char *icon)
3434 {
3435     Arg args[16];
3436     int i;
3437     if (appData.titleInWindow) {
3438         i = 0;
3439         XtSetArg(args[i], XtNlabel, text);   i++;
3440         XtSetValues(titleWidget, args, i);
3441     }
3442     i = 0;
3443     XtSetArg(args[i], XtNiconName, (XtArgVal) icon);    i++;
3444     XtSetArg(args[i], XtNtitle, (XtArgVal) title);      i++;
3445     XtSetValues(shellWidget, args, i);
3446     XSync(xDisplay, False);
3447 }
3448
3449
3450 static int
3451 NullXErrorCheck (Display *dpy, XErrorEvent *error_event)
3452 {
3453     return 0;
3454 }
3455
3456 void
3457 DisplayIcsInteractionTitle (String message)
3458 {
3459   if (oldICSInteractionTitle == NULL) {
3460     /* Magic to find the old window title, adapted from vim */
3461     char *wina = getenv("WINDOWID");
3462     if (wina != NULL) {
3463       Window win = (Window) atoi(wina);
3464       Window root, parent, *children;
3465       unsigned int nchildren;
3466       int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
3467       for (;;) {
3468         if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
3469         if (!XQueryTree(xDisplay, win, &root, &parent,
3470                         &children, &nchildren)) break;
3471         if (children) XFree((void *)children);
3472         if (parent == root || parent == 0) break;
3473         win = parent;
3474       }
3475       XSetErrorHandler(oldHandler);
3476     }
3477     if (oldICSInteractionTitle == NULL) {
3478       oldICSInteractionTitle = "xterm";
3479     }
3480   }
3481   printf("\033]0;%s\007", message);
3482   fflush(stdout);
3483 }
3484
3485
3486 XtIntervalId delayedEventTimerXID = 0;
3487 DelayedEventCallback delayedEventCallback = 0;
3488
3489 void
3490 FireDelayedEvent ()
3491 {
3492     delayedEventTimerXID = 0;
3493     delayedEventCallback();
3494 }
3495
3496 void
3497 ScheduleDelayedEvent (DelayedEventCallback cb, long millisec)
3498 {
3499     if(delayedEventTimerXID && delayedEventCallback == cb)
3500         // [HGM] alive: replace, rather than add or flush identical event
3501         XtRemoveTimeOut(delayedEventTimerXID);
3502     delayedEventCallback = cb;
3503     delayedEventTimerXID =
3504       XtAppAddTimeOut(appContext, millisec,
3505                       (XtTimerCallbackProc) FireDelayedEvent, (XtPointer) 0);
3506 }
3507
3508 DelayedEventCallback
3509 GetDelayedEvent ()
3510 {
3511   if (delayedEventTimerXID) {
3512     return delayedEventCallback;
3513   } else {
3514     return NULL;
3515   }
3516 }
3517
3518 void
3519 CancelDelayedEvent ()
3520 {
3521   if (delayedEventTimerXID) {
3522     XtRemoveTimeOut(delayedEventTimerXID);
3523     delayedEventTimerXID = 0;
3524   }
3525 }
3526
3527 XtIntervalId loadGameTimerXID = 0;
3528
3529 int
3530 LoadGameTimerRunning ()
3531 {
3532     return loadGameTimerXID != 0;
3533 }
3534
3535 int
3536 StopLoadGameTimer ()
3537 {
3538     if (loadGameTimerXID != 0) {
3539         XtRemoveTimeOut(loadGameTimerXID);
3540         loadGameTimerXID = 0;
3541         return TRUE;
3542     } else {
3543         return FALSE;
3544     }
3545 }
3546
3547 void
3548 LoadGameTimerCallback (XtPointer arg, XtIntervalId *id)
3549 {
3550     loadGameTimerXID = 0;
3551     AutoPlayGameLoop();
3552 }
3553
3554 void
3555 StartLoadGameTimer (long millisec)
3556 {
3557     loadGameTimerXID =
3558       XtAppAddTimeOut(appContext, millisec,
3559                       (XtTimerCallbackProc) LoadGameTimerCallback,
3560                       (XtPointer) 0);
3561 }
3562
3563 XtIntervalId analysisClockXID = 0;
3564
3565 void
3566 AnalysisClockCallback (XtPointer arg, XtIntervalId *id)
3567 {
3568     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
3569          || appData.icsEngineAnalyze) { // [DM]
3570         AnalysisPeriodicEvent(0);
3571         StartAnalysisClock();
3572     }
3573 }
3574
3575 void
3576 StartAnalysisClock ()
3577 {
3578     analysisClockXID =
3579       XtAppAddTimeOut(appContext, 2000,
3580                       (XtTimerCallbackProc) AnalysisClockCallback,
3581                       (XtPointer) 0);
3582 }
3583
3584 XtIntervalId clockTimerXID = 0;
3585
3586 int
3587 ClockTimerRunning ()
3588 {
3589     return clockTimerXID != 0;
3590 }
3591
3592 int
3593 StopClockTimer ()
3594 {
3595     if (clockTimerXID != 0) {
3596         XtRemoveTimeOut(clockTimerXID);
3597         clockTimerXID = 0;
3598         return TRUE;
3599     } else {
3600         return FALSE;
3601     }
3602 }
3603
3604 void
3605 ClockTimerCallback (XtPointer arg, XtIntervalId *id)
3606 {
3607     clockTimerXID = 0;
3608     DecrementClocks();
3609 }
3610
3611 void
3612 StartClockTimer (long millisec)
3613 {
3614     clockTimerXID =
3615       XtAppAddTimeOut(appContext, millisec,
3616                       (XtTimerCallbackProc) ClockTimerCallback,
3617                       (XtPointer) 0);
3618 }
3619
3620 void
3621 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
3622 {
3623     char buf[MSG_SIZ];
3624     Arg args[16];
3625     Widget w = (Widget) opt->handle;
3626
3627     /* check for low time warning */
3628     Pixel foregroundOrWarningColor = timerForegroundPixel;
3629
3630     if (timer > 0 &&
3631         appData.lowTimeWarning &&
3632         (timer / 1000) < appData.icsAlarmTime)
3633       foregroundOrWarningColor = lowTimeWarningColor;
3634
3635     if (appData.clockMode) {
3636       snprintf(buf, MSG_SIZ, "%s:%s%s", color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
3637       XtSetArg(args[0], XtNlabel, buf);
3638     } else {
3639       snprintf(buf, MSG_SIZ, "%s  ", color);
3640       XtSetArg(args[0], XtNlabel, buf);
3641     }
3642
3643     if (highlight) {
3644
3645         XtSetArg(args[1], XtNbackground, foregroundOrWarningColor);
3646         XtSetArg(args[2], XtNforeground, timerBackgroundPixel);
3647     } else {
3648         XtSetArg(args[1], XtNbackground, timerBackgroundPixel);
3649         XtSetArg(args[2], XtNforeground, foregroundOrWarningColor);
3650     }
3651
3652     XtSetValues(w, args, 3);
3653 }
3654
3655 static Pixmap *clockIcons[] = { &wIconPixmap, &bIconPixmap };
3656
3657 void
3658 SetClockIcon (int color)
3659 {
3660     Arg args[16];
3661     Pixmap pm = *clockIcons[color];
3662     if (iconPixmap != pm) {
3663         iconPixmap = pm;
3664         XtSetArg(args[0], XtNiconPixmap, iconPixmap);
3665         XtSetValues(shellWidget, args, 1);
3666     }
3667 }
3668
3669 void
3670 DoInputCallback (caddr_t closure, int *source, XtInputId *xid)
3671 {
3672     InputSource *is = (InputSource *) closure;
3673     int count;
3674     int error;
3675     char *p, *q;
3676
3677     if (is->lineByLine) {
3678         count = read(is->fd, is->unused,
3679                      INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
3680         if (count <= 0) {
3681             (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
3682             return;
3683         }
3684         is->unused += count;
3685         p = is->buf;
3686         while (p < is->unused) {
3687             q = memchr(p, '\n', is->unused - p);
3688             if (q == NULL) break;
3689             q++;
3690             (is->func)(is, is->closure, p, q - p, 0);
3691             p = q;
3692         }
3693         q = is->buf;
3694         while (p < is->unused) {
3695             *q++ = *p++;
3696         }
3697         is->unused = q;
3698     } else {
3699         count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
3700         if (count == -1)
3701           error = errno;
3702         else
3703           error = 0;
3704         (is->func)(is, is->closure, is->buf, count, error);
3705     }
3706 }
3707
3708 InputSourceRef
3709 AddInputSource (ProcRef pr, int lineByLine, InputCallback func, VOIDSTAR closure)
3710 {
3711     InputSource *is;
3712     ChildProc *cp = (ChildProc *) pr;
3713
3714     is = (InputSource *) calloc(1, sizeof(InputSource));
3715     is->lineByLine = lineByLine;
3716     is->func = func;
3717     if (pr == NoProc) {
3718         is->kind = CPReal;
3719         is->fd = fileno(stdin);
3720     } else {
3721         is->kind = cp->kind;
3722         is->fd = cp->fdFrom;
3723     }
3724     if (lineByLine) {
3725         is->unused = is->buf;
3726     }
3727
3728     is->xid = XtAppAddInput(appContext, is->fd,
3729                             (XtPointer) (XtInputReadMask),
3730                             (XtInputCallbackProc) DoInputCallback,
3731                             (XtPointer) is);
3732     is->closure = closure;
3733     return (InputSourceRef) is;
3734 }
3735
3736 void
3737 RemoveInputSource (InputSourceRef isr)
3738 {
3739     InputSource *is = (InputSource *) isr;
3740
3741     if (is->xid == 0) return;
3742     XtRemoveInput(is->xid);
3743     is->xid = 0;
3744 }
3745
3746 /****   Animation code by Hugh Fisher, DCS, ANU. ****/
3747
3748 /*      Masks for XPM pieces. Black and white pieces can have
3749         different shapes, but in the interest of retaining my
3750         sanity pieces must have the same outline on both light
3751         and dark squares, and all pieces must use the same
3752         background square colors/images.                */
3753
3754 static int xpmDone = 0;
3755 static Pixmap animBufs[3*NrOfAnims]; // newBuf, saveBuf
3756 static GC animGCs[3*NrOfAnims]; // blitGC, pieceGC, outlineGC;
3757 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
3758
3759 static void
3760 CreateAnimMasks (int pieceDepth)
3761 {
3762   ChessSquare   piece;
3763   Pixmap        buf;
3764   GC            bufGC, maskGC;
3765   int           kind, n;
3766   unsigned long plane;
3767   XGCValues     values;
3768
3769   /* Need a bitmap just to get a GC with right depth */
3770   buf = XCreatePixmap(xDisplay, xBoardWindow,
3771                         8, 8, 1);
3772   values.foreground = 1;
3773   values.background = 0;
3774   /* Don't use XtGetGC, not read only */
3775   maskGC = XCreateGC(xDisplay, buf,
3776                     GCForeground | GCBackground, &values);
3777   XFreePixmap(xDisplay, buf);
3778
3779   buf = XCreatePixmap(xDisplay, xBoardWindow,
3780                       squareSize, squareSize, pieceDepth);
3781   values.foreground = XBlackPixel(xDisplay, xScreen);
3782   values.background = XWhitePixel(xDisplay, xScreen);
3783   bufGC = XCreateGC(xDisplay, buf,
3784                     GCForeground | GCBackground, &values);
3785
3786   for (piece = WhitePawn; piece <= BlackKing; piece++) {
3787     /* Begin with empty mask */
3788     if(!xpmDone) // [HGM] pieces: keep using existing
3789     xpmMask[piece] = XCreatePixmap(xDisplay, xBoardWindow,
3790                                  squareSize, squareSize, 1);
3791     XSetFunction(xDisplay, maskGC, GXclear);
3792     XFillRectangle(xDisplay, xpmMask[piece], maskGC,
3793                    0, 0, squareSize, squareSize);
3794
3795     /* Take a copy of the piece */
3796     if (White(piece))
3797       kind = 0;
3798     else
3799       kind = 2;
3800     XSetFunction(xDisplay, bufGC, GXcopy);
3801     XCopyArea(xDisplay, xpmPieceBitmap[kind][((int)piece) % (int)BlackPawn],
3802               buf, bufGC,
3803               0, 0, squareSize, squareSize, 0, 0);
3804
3805     /* XOR the background (light) over the piece */
3806     XSetFunction(xDisplay, bufGC, GXxor);
3807     if (useImageSqs)
3808       XCopyArea(xDisplay, xpmLightSquare, buf, bufGC,
3809                 0, 0, squareSize, squareSize, 0, 0);
3810     else {
3811       XSetForeground(xDisplay, bufGC, lightSquareColor);
3812       XFillRectangle(xDisplay, buf, bufGC, 0, 0, squareSize, squareSize);
3813     }
3814
3815     /* We now have an inverted piece image with the background
3816        erased. Construct mask by just selecting all the non-zero
3817        pixels - no need to reconstruct the original image.      */
3818     XSetFunction(xDisplay, maskGC, GXor);
3819     plane = 1;
3820     /* Might be quicker to download an XImage and create bitmap
3821        data from it rather than this N copies per piece, but it
3822        only takes a fraction of a second and there is a much
3823        longer delay for loading the pieces.             */
3824     for (n = 0; n < pieceDepth; n ++) {
3825       XCopyPlane(xDisplay, buf, xpmMask[piece], maskGC,
3826                  0, 0, squareSize, squareSize,
3827                  0, 0, plane);
3828       plane = plane << 1;
3829     }
3830   }
3831   /* Clean up */
3832   XFreePixmap(xDisplay, buf);
3833   XFreeGC(xDisplay, bufGC);
3834   XFreeGC(xDisplay, maskGC);
3835 }
3836
3837 static void
3838 InitAnimState (AnimNr anr, XWindowAttributes *info)
3839 {
3840   XtGCMask  mask;
3841   XGCValues values;
3842
3843   if(cairoAnimate) {
3844     DrawSeekOpen(); // set cs to board widget
3845     c_animBufs[anr+4] = csBoardWindow;
3846     c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
3847     c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
3848   }
3849
3850   /* Each buffer is square size, same depth as window */
3851   animBufs[anr+4] = xBoardWindow;
3852   animBufs[anr+2] = XCreatePixmap(xDisplay, xBoardWindow,
3853                         squareSize, squareSize, info->depth);
3854   animBufs[anr] = XCreatePixmap(xDisplay, xBoardWindow,
3855                         squareSize, squareSize, info->depth);
3856
3857   /* Create a plain GC for blitting */
3858   mask = GCForeground | GCBackground | GCFunction |
3859          GCPlaneMask | GCGraphicsExposures;
3860   values.foreground = XBlackPixel(xDisplay, xScreen);
3861   values.background = XWhitePixel(xDisplay, xScreen);
3862   values.function   = GXcopy;
3863   values.plane_mask = AllPlanes;
3864   values.graphics_exposures = False;
3865   animGCs[anr] = XCreateGC(xDisplay, xBoardWindow, mask, &values);
3866
3867   /* Piece will be copied from an existing context at
3868      the start of each new animation/drag. */
3869   animGCs[anr+2] = XCreateGC(xDisplay, xBoardWindow, 0, &values);
3870
3871   /* Outline will be a read-only copy of an existing */
3872   animGCs[anr+4] = None;
3873 }
3874
3875 void
3876 CreateAnimVars ()
3877 {
3878   XWindowAttributes info;
3879
3880   if (xpmDone && gameInfo.variant == oldVariant) return;
3881   if(xpmDone) oldVariant = gameInfo.variant; // first time pieces might not be created yet
3882   XGetWindowAttributes(xDisplay, xBoardWindow, &info);
3883
3884   InitAnimState(Game, &info);
3885   InitAnimState(Player, &info);
3886
3887   /* For XPM pieces, we need bitmaps to use as masks. */
3888   if (useImages)
3889     CreateAnimMasks(info.depth), xpmDone = 1;
3890 }
3891
3892 #ifndef HAVE_USLEEP
3893
3894 static Boolean frameWaiting;
3895
3896 static RETSIGTYPE
3897 FrameAlarm (int sig)
3898 {
3899   frameWaiting = False;
3900   /* In case System-V style signals.  Needed?? */
3901   signal(SIGALRM, FrameAlarm);
3902 }
3903
3904 void
3905 FrameDelay (int time)
3906 {
3907   struct itimerval delay;
3908
3909   XSync(xDisplay, False);
3910
3911   if (time > 0) {
3912     frameWaiting = True;
3913     signal(SIGALRM, FrameAlarm);
3914     delay.it_interval.tv_sec =
3915       delay.it_value.tv_sec = time / 1000;
3916     delay.it_interval.tv_usec =
3917       delay.it_value.tv_usec = (time % 1000) * 1000;
3918     setitimer(ITIMER_REAL, &delay, NULL);
3919     while (frameWaiting) pause();
3920     delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
3921     delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
3922     setitimer(ITIMER_REAL, &delay, NULL);
3923   }
3924 }
3925
3926 #else
3927
3928 void
3929 FrameDelay (int time)
3930 {
3931   XSync(xDisplay, False);
3932   if (time > 0)
3933     usleep(time * 1000);
3934 }
3935
3936 #endif
3937
3938 static void
3939 SelectGCMask (ChessSquare piece, GC *clip, GC *outline, Pixmap *mask)
3940 {
3941   GC source;
3942
3943   /* Bitmap for piece being moved. */
3944   if (appData.monoMode) {
3945       *mask = *pieceToSolid(piece);
3946   } else if (useImages) {
3947 #if HAVE_LIBXPM
3948       *mask = xpmMask[piece];
3949 #else
3950       *mask = ximMaskPm[piece];
3951 #endif
3952   } else {
3953       *mask = *pieceToSolid(piece);
3954   }
3955
3956   /* GC for piece being moved. Square color doesn't matter, but
3957      since it gets modified we make a copy of the original. */
3958   if (White(piece)) {
3959     if (appData.monoMode)
3960       source = bwPieceGC;
3961     else
3962       source = wlPieceGC;
3963   } else {
3964     if (appData.monoMode)
3965       source = wbPieceGC;
3966     else
3967       source = blPieceGC;
3968   }
3969   XCopyGC(xDisplay, source, 0xFFFFFFFF, *clip);
3970
3971   /* Outline only used in mono mode and is not modified */
3972   if (White(piece))
3973     *outline = bwPieceGC;
3974   else
3975     *outline = wbPieceGC;
3976 }
3977
3978 static void
3979 OverlayPiece (ChessSquare piece, GC clip, GC outline,  Drawable dest)
3980 {
3981   int   kind;
3982
3983   if (!useImages) {
3984     /* Draw solid rectangle which will be clipped to shape of piece */
3985     XFillRectangle(xDisplay, dest, clip,
3986                    0, 0, squareSize, squareSize);
3987     if (appData.monoMode)
3988       /* Also draw outline in contrasting color for black
3989          on black / white on white cases                */
3990       XCopyPlane(xDisplay, *pieceToOutline(piece), dest, outline,
3991                  0, 0, squareSize, squareSize, 0, 0, 1);
3992   } else {
3993     /* Copy the piece */
3994     if (White(piece))
3995       kind = 0;
3996     else
3997       kind = 2;
3998     if(appData.upsideDown && flipView) kind ^= 2;
3999     XCopyArea(xDisplay, xpmPieceBitmap[kind][piece],
4000               dest, clip,
4001               0, 0, squareSize, squareSize,
4002               0, 0);
4003   }
4004 }
4005
4006 static void
4007 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
4008 {
4009   static ChessSquare oldPiece = -1;
4010   static cairo_t *pieceSource;
4011   if(piece != oldPiece) { // try make it faster by only changing cr if we need other piece
4012     if(pieceSource) cairo_destroy (pieceSource);
4013     pieceSource = cairo_create (dest);
4014     cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
4015     oldPiece = piece;
4016   }
4017   cairo_paint(pieceSource);
4018 }
4019
4020 void
4021 InsertPiece (AnimNr anr, ChessSquare piece)
4022 {
4023   if(cairoAnimate) {
4024     CairoOverlayPiece(piece, c_animBufs[anr]);
4025   } else
4026   OverlayPiece(piece, animGCs[anr+2], animGCs[anr+4], animBufs[anr]);
4027 }
4028
4029 void
4030 DrawBlank (AnimNr anr, int x, int y, int startColor)
4031 {
4032     if(cairoAnimate)
4033     BlankSquare(x, y, startColor, EmptySquare, (Drawable) c_animBufs[anr+2], 0);
4034     else
4035     BlankSquare(x, y, startColor, EmptySquare, animBufs[anr+2], 0);
4036 }
4037
4038 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
4039                  int srcX, int srcY, int width, int height, int destX, int destY)
4040 {
4041     if(cairoAnimate) {
4042         cairo_t *cr = cairo_create (c_animBufs[anr+destBuf]);
4043         cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
4044         cairo_rectangle (cr, destX, destY, width, height);
4045         cairo_fill (cr);
4046         cairo_destroy (cr);
4047     } else
4048     XCopyArea(xDisplay, animBufs[anr+srcBuf], animBufs[anr+destBuf], animGCs[anr],
4049                 srcX, srcY, width, height, destX, destY);
4050 }
4051
4052 void
4053 SetDragPiece (AnimNr anr, ChessSquare piece)
4054 {
4055   Pixmap mask;
4056   if(cairoAnimate) return;
4057   /* The piece will be drawn using its own bitmap as a matte    */
4058   SelectGCMask(piece, &animGCs[anr+2], &animGCs[anr+4], &mask);
4059   XSetClipMask(xDisplay, animGCs[anr+2], mask);
4060 }
4061
4062 /* [AS] Arrow highlighting support */
4063
4064 void DrawPolygon(Pnt arrow[], int nr)
4065 {   // for now on own surface; eventually this should become a global that is only destroyed on resize
4066     cairo_surface_t *boardSurface;
4067     cairo_t *cr;
4068     int i;
4069     int w = lineGap + BOARD_WIDTH * (squareSize + lineGap);
4070     int h = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
4071     boardSurface = cairo_xlib_surface_create(xDisplay, xBoardWindow, DefaultVisual(xDisplay, 0), w, h);
4072     cr = cairo_create (boardSurface);
4073     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
4074     for (i=0;i<nr;i++) {
4075         cairo_line_to(cr, arrow[i].x, arrow[i].y);
4076     }
4077     if(appData.monoMode) { // should we always outline arrow?
4078         cairo_line_to(cr, arrow[0].x, arrow[0].y);
4079         SetPen(cr, 2, "#000000", 0);
4080         cairo_stroke_preserve(cr);
4081     }
4082     SetPen(cr, 2, appData.highlightSquareColor, 0);
4083     cairo_fill(cr);
4084
4085     /* free memory */
4086     cairo_destroy (cr);
4087     cairo_surface_destroy (boardSurface);
4088 }
4089
4090 static void
4091 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
4092 {
4093     char buf[MSG_SIZ], *logoName = buf;
4094     if(appData.logo[n][0]) {
4095         logoName = appData.logo[n];
4096     } else if(appData.autoLogo) {
4097         if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
4098             sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
4099         } else if(appData.directory[n] && appData.directory[n][0]) {
4100             sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
4101         }
4102     }
4103     if(logoName[0])
4104         { ASSIGN(cps->programLogo, logoName); }
4105 }
4106
4107 void
4108 UpdateLogos (int displ)
4109 {
4110     if(optList[W_WHITE-1].handle == NULL) return;
4111     LoadLogo(&first, 0, 0);
4112     LoadLogo(&second, 1, appData.icsActive);
4113     if(displ) DisplayLogos(optList[W_WHITE-1].handle, optList[W_BLACK+1].handle);
4114     return;
4115 }
4116