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