f344499c5741d13913ce5c36567945023c6ba8ac
[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
65 #if !OMIT_SOCKETS
66 # if HAVE_SYS_SOCKET_H
67 #  include <sys/socket.h>
68 #  include <netinet/in.h>
69 #  include <netdb.h>
70 # else /* not HAVE_SYS_SOCKET_H */
71 #  if HAVE_LAN_SOCKET_H
72 #   include <lan/socket.h>
73 #   include <lan/in.h>
74 #   include <lan/netdb.h>
75 #  else /* not HAVE_LAN_SOCKET_H */
76 #   define OMIT_SOCKETS 1
77 #  endif /* not HAVE_LAN_SOCKET_H */
78 # endif /* not HAVE_SYS_SOCKET_H */
79 #endif /* !OMIT_SOCKETS */
80
81 #if STDC_HEADERS
82 # include <stdlib.h>
83 # include <string.h>
84 #else /* not STDC_HEADERS */
85 extern char *getenv();
86 # if HAVE_STRING_H
87 #  include <string.h>
88 # else /* not HAVE_STRING_H */
89 #  include <strings.h>
90 # endif /* not HAVE_STRING_H */
91 #endif /* not STDC_HEADERS */
92
93 #if HAVE_SYS_FCNTL_H
94 # include <sys/fcntl.h>
95 #else /* not HAVE_SYS_FCNTL_H */
96 # if HAVE_FCNTL_H
97 #  include <fcntl.h>
98 # endif /* HAVE_FCNTL_H */
99 #endif /* not HAVE_SYS_FCNTL_H */
100
101 #if HAVE_SYS_SYSTEMINFO_H
102 # include <sys/systeminfo.h>
103 #endif /* HAVE_SYS_SYSTEMINFO_H */
104
105 #if TIME_WITH_SYS_TIME
106 # include <sys/time.h>
107 # include <time.h>
108 #else
109 # if HAVE_SYS_TIME_H
110 #  include <sys/time.h>
111 # else
112 #  include <time.h>
113 # endif
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #if HAVE_SYS_WAIT_H
121 # include <sys/wait.h>
122 #endif
123
124 #if HAVE_DIRENT_H
125 # include <dirent.h>
126 # define NAMLEN(dirent) strlen((dirent)->d_name)
127 # define HAVE_DIR_STRUCT
128 #else
129 # define dirent direct
130 # define NAMLEN(dirent) (dirent)->d_namlen
131 # if HAVE_SYS_NDIR_H
132 #  include <sys/ndir.h>
133 #  define HAVE_DIR_STRUCT
134 # endif
135 # if HAVE_SYS_DIR_H
136 #  include <sys/dir.h>
137 #  define HAVE_DIR_STRUCT
138 # endif
139 # if HAVE_NDIR_H
140 #  include <ndir.h>
141 #  define HAVE_DIR_STRUCT
142 # endif
143 #endif
144
145 #if ENABLE_NLS
146 #include <locale.h>
147 #endif
148
149 #include <X11/Intrinsic.h>
150 #include <X11/StringDefs.h>
151 #include <X11/Shell.h>
152 #include <X11/cursorfont.h>
153 #include <X11/Xatom.h>
154 #include <X11/Xmu/Atoms.h>
155 #if USE_XAW3D
156 #include <X11/Xaw3d/Dialog.h>
157 #include <X11/Xaw3d/Form.h>
158 #include <X11/Xaw3d/List.h>
159 #include <X11/Xaw3d/Label.h>
160 #include <X11/Xaw3d/SimpleMenu.h>
161 #include <X11/Xaw3d/SmeBSB.h>
162 #include <X11/Xaw3d/SmeLine.h>
163 #include <X11/Xaw3d/Box.h>
164 #include <X11/Xaw3d/MenuButton.h>
165 #include <X11/Xaw3d/Text.h>
166 #include <X11/Xaw3d/AsciiText.h>
167 #else
168 #include <X11/Xaw/Dialog.h>
169 #include <X11/Xaw/Form.h>
170 #include <X11/Xaw/List.h>
171 #include <X11/Xaw/Label.h>
172 #include <X11/Xaw/SimpleMenu.h>
173 #include <X11/Xaw/SmeBSB.h>
174 #include <X11/Xaw/SmeLine.h>
175 #include <X11/Xaw/Box.h>
176 #include <X11/Xaw/MenuButton.h>
177 #include <X11/Xaw/Text.h>
178 #include <X11/Xaw/AsciiText.h>
179 #endif
180
181 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
182 #include "common.h"
183
184 #if HAVE_LIBXPM
185 #include <X11/xpm.h>
186 #include "pixmaps/pixmaps.h"
187 #define IMAGE_EXT "xpm"
188 #else
189 #define IMAGE_EXT "xim"
190 #include "bitmaps/bitmaps.h"
191 #endif
192
193 #include "bitmaps/icon_white.bm"
194 #include "bitmaps/icon_black.bm"
195 #include "bitmaps/checkmark.bm"
196
197 #include "frontend.h"
198 #include "backend.h"
199 #include "backendz.h"
200 #include "moves.h"
201 #include "xboard.h"
202 #include "childio.h"
203 #include "xgamelist.h"
204 #include "xhistory.h"
205 #include "xedittags.h"
206 #include "menus.h"
207 #include "gettext.h"
208
209
210 #ifdef __EMX__
211 #ifndef HAVE_USLEEP
212 #define HAVE_USLEEP
213 #endif
214 #define usleep(t)   _sleep2(((t)+500)/1000)
215 #endif
216
217 #ifdef ENABLE_NLS
218 # define  _(s) gettext (s)
219 # define N_(s) gettext_noop (s)
220 #else
221 # define  _(s) (s)
222 # define N_(s)  s
223 #endif
224
225 int main P((int argc, char **argv));
226 FILE * XsraSelFile P((Widget w, char *prompt, char *ok, char *cancel, char *failed,
227                 char *init_path, char *filter, char *mode, int (*show_entry)(), char **name_return));
228 RETSIGTYPE CmailSigHandler P((int sig));
229 RETSIGTYPE IntSigHandler P((int sig));
230 RETSIGTYPE TermSizeSigHandler P((int sig));
231 void CreateGCs P((int redo));
232 void CreateAnyPieces P((void));
233 void CreateXIMPieces P((void));
234 void CreateXPMPieces P((void));
235 void CreateXPMBoard P((char *s, int n));
236 void CreatePieces P((void));
237 void CreatePieceMenus P((void));
238 Widget CreateMenuBar P((Menu *mb, int boardWidth));
239 Widget CreateButtonBar P ((MenuItem *mi));
240 #if ENABLE_NLS
241 char *InsertPxlSize P((char *pattern, int targetPxlSize));
242 XFontSet CreateFontSet P((char *base_fnt_lst));
243 #else
244 char *FindFont P((char *pattern, int targetPxlSize));
245 #endif
246 void PieceMenuPopup P((Widget w, XEvent *event,
247                        String *params, Cardinal *num_params));
248 static void PieceMenuSelect P((Widget w, ChessSquare piece, caddr_t junk));
249 static void DropMenuSelect P((Widget w, ChessSquare piece, caddr_t junk));
250 void ReadBitmap P((Pixmap *pm, String name, unsigned char bits[],
251                    u_int wreq, u_int hreq));
252 void CreateGrid P((void));
253 int EventToSquare P((int x, int limit));
254 void DrawSquare P((int row, int column, ChessSquare piece, int do_flash));
255 void EventProc P((Widget widget, caddr_t unused, XEvent *event));
256 void DelayedDrag P((void));
257 void MoveTypeInProc P((Widget widget, caddr_t unused, XEvent *event));
258 void HandleUserMove P((Widget w, XEvent *event,
259                      String *prms, Cardinal *nprms));
260 void AnimateUserMove P((Widget w, XEvent * event,
261                      String * params, Cardinal * nParams));
262 void HandlePV P((Widget w, XEvent * event,
263                      String * params, Cardinal * nParams));
264 void SelectPV P((Widget w, XEvent * event,
265                      String * params, Cardinal * nParams));
266 void StopPV P((Widget w, XEvent * event,
267                      String * params, Cardinal * nParams));
268 void WhiteClock P((Widget w, XEvent *event,
269                    String *prms, Cardinal *nprms));
270 void BlackClock P((Widget w, XEvent *event,
271                    String *prms, Cardinal *nprms));
272 void DrawPositionProc P((Widget w, XEvent *event,
273                      String *prms, Cardinal *nprms));
274 void XDrawPosition P((Widget w, /*Boolean*/int repaint,
275                      Board board));
276 void CommentClick P((Widget w, XEvent * event,
277                    String * params, Cardinal * nParams));
278 void CommentPopUp P((char *title, char *label));
279 void CommentPopDown P((void));
280 void ICSInputBoxPopUp P((void));
281 void ICSInputBoxPopDown P((void));
282 void FileNamePopUp P((char *label, char *def, char *filter,
283                       FileProc proc, char *openMode));
284 void FileNamePopDown P((void));
285 void FileNameCallback P((Widget w, XtPointer client_data,
286                          XtPointer call_data));
287 void FileNameAction P((Widget w, XEvent *event,
288                        String *prms, Cardinal *nprms));
289 void AskQuestionReplyAction P((Widget w, XEvent *event,
290                           String *prms, Cardinal *nprms));
291 void AskQuestionProc P((Widget w, XEvent *event,
292                           String *prms, Cardinal *nprms));
293 void AskQuestionPopDown P((void));
294 void PromotionPopDown P((void));
295 void PromotionCallback P((Widget w, XtPointer client_data,
296                           XtPointer call_data));
297 void SelectCommand P((Widget w, XtPointer client_data, XtPointer call_data));
298 void KeyBindingProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
299 void QuitWrapper P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
300 void TypeInProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
301 void EnterKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
302 void UpKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
303 void DownKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
304 void TempBackwardProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
305 void TempForwardProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
306 Boolean TempBackwardActive = False;
307 void ManInner P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
308 void DisplayMove P((int moveNumber));
309 void DisplayTitle P((char *title));
310 void ICSInitScript P((void));
311 int LoadGamePopUp P((FILE *f, int gameNumber, char *title));
312 void ErrorPopUp P((char *title, char *text, int modal));
313 void ErrorPopDown P((void));
314 static char *ExpandPathName P((char *path));
315 static void CreateAnimVars P((void));
316 static void DragPieceMove P((int x, int y));
317 static void DrawDragPiece P((void));
318 char *ModeToWidgetName P((GameMode mode));
319 void SelectMove P((Widget w, XEvent * event, String * params, Cardinal * nParams));
320 void GameListOptionsPopDown P(());
321 void GenericPopDown P(());
322 void update_ics_width P(());
323 int get_term_width P(());
324 int CopyMemoProc P(());
325 void DrawArrowHighlight P((int fromX, int fromY, int toX,int toY));
326 Boolean IsDrawArrowEnabled P(());
327
328 /*
329 * XBoard depends on Xt R4 or higher
330 */
331 int xtVersion = XtSpecificationRelease;
332
333 int xScreen;
334 Display *xDisplay;
335 Window xBoardWindow;
336 Pixel lightSquareColor, darkSquareColor, whitePieceColor, blackPieceColor,
337   jailSquareColor, highlightSquareColor, premoveHighlightColor;
338 Pixel lowTimeWarningColor;
339 GC lightSquareGC, darkSquareGC, jailSquareGC, lineGC, wdPieceGC, wlPieceGC,
340   bdPieceGC, blPieceGC, wbPieceGC, bwPieceGC, coordGC, highlineGC,
341   wjPieceGC, bjPieceGC, prelineGC, countGC;
342 Pixmap iconPixmap, wIconPixmap, bIconPixmap, xMarkPixmap;
343 Widget shellWidget, layoutWidget, formWidget, boardWidget, messageWidget,
344   whiteTimerWidget, blackTimerWidget, titleWidget, widgetList[16],
345   commentShell, promotionShell, whitePieceMenu, blackPieceMenu, dropMenu,
346   menuBarWidget, buttonBarWidget, editShell, errorShell, analysisShell,
347   ICSInputShell, fileNameShell, askQuestionShell;
348 Widget historyShell, evalGraphShell, gameListShell;
349 int hOffset; // [HGM] dual
350 XSegment secondSegments[BOARD_RANKS + BOARD_FILES + 2];
351 XSegment gridSegments[BOARD_RANKS + BOARD_FILES + 2];
352 XSegment jailGridSegments[BOARD_RANKS + BOARD_FILES + 6];
353 #if ENABLE_NLS
354 XFontSet fontSet, clockFontSet;
355 #else
356 Font clockFontID;
357 XFontStruct *clockFontStruct;
358 #endif
359 Font coordFontID, countFontID;
360 XFontStruct *coordFontStruct, *countFontStruct;
361 XtAppContext appContext;
362 char *layoutName;
363 char *oldICSInteractionTitle;
364
365 FileProc fileProc;
366 char *fileOpenMode;
367 char installDir[] = "."; // [HGM] UCI: needed for UCI; probably needs run-time initializtion
368
369 Position commentX = -1, commentY = -1;
370 Dimension commentW, commentH;
371 typedef unsigned int BoardSize;
372 BoardSize boardSize;
373 Boolean chessProgram;
374
375 int  minX, minY; // [HGM] placement: volatile limits on upper-left corner
376 int squareSize, smallLayout = 0, tinyLayout = 0,
377   marginW, marginH, // [HGM] for run-time resizing
378   fromX = -1, fromY = -1, toX, toY, commentUp = False, analysisUp = False,
379   ICSInputBoxUp = False, askQuestionUp = False,
380   filenameUp = False, promotionUp = False, pmFromX = -1, pmFromY = -1,
381   errorUp = False, errorExitStatus = -1, lineGap, defaultLineGap;
382 Dimension textHeight;
383 Pixel timerForegroundPixel, timerBackgroundPixel;
384 Pixel buttonForegroundPixel, buttonBackgroundPixel;
385 char *chessDir, *programName, *programVersion,
386   *gameCopyFilename, *gamePasteFilename;
387 Boolean alwaysOnTop = False;
388 Boolean saveSettingsOnExit;
389 char *settingsFileName;
390 char *icsTextMenuString;
391 char *icsNames;
392 char *firstChessProgramNames;
393 char *secondChessProgramNames;
394
395 WindowPlacement wpMain;
396 WindowPlacement wpConsole;
397 WindowPlacement wpComment;
398 WindowPlacement wpMoveHistory;
399 WindowPlacement wpEvalGraph;
400 WindowPlacement wpEngineOutput;
401 WindowPlacement wpGameList;
402 WindowPlacement wpTags;
403
404 extern Widget shells[];
405 extern Boolean shellUp[];
406
407 #define SOLID 0
408 #define OUTLINE 1
409 Pixmap pieceBitmap[2][(int)BlackPawn];
410 Pixmap pieceBitmap2[2][(int)BlackPawn+4];       /* [HGM] pieces */
411 Pixmap xpmPieceBitmap[4][(int)BlackPawn];       /* LL, LD, DL, DD actually used*/
412 Pixmap xpmPieceBitmap2[4][(int)BlackPawn+4];    /* LL, LD, DL, DD set to select from */
413 Pixmap xpmLightSquare, xpmDarkSquare, xpmJailSquare;
414 Pixmap xpmBoardBitmap[2];
415 int useImages, useImageSqs, useTexture, textureW[2], textureH[2];
416 XImage *ximPieceBitmap[4][(int)BlackPawn+4];    /* LL, LD, DL, DD */
417 Pixmap ximMaskPm[(int)BlackPawn];               /* clipmasks, used for XIM pieces */
418 Pixmap ximMaskPm2[(int)BlackPawn+4];            /* clipmasks, used for XIM pieces */
419 XImage *ximLightSquare, *ximDarkSquare;
420 XImage *xim_Cross;
421
422 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
423 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
424
425 #define White(piece) ((int)(piece) < (int)BlackPawn)
426
427 /* Variables for doing smooth animation. This whole thing
428    would be much easier if the board was double-buffered,
429    but that would require a fairly major rewrite.       */
430
431 typedef struct {
432         Pixmap  saveBuf;
433         Pixmap  newBuf;
434         GC      blitGC, pieceGC, outlineGC;
435         XPoint  startSquare, prevFrame, mouseDelta;
436         int     startColor;
437         int     dragPiece;
438         Boolean dragActive;
439         int     startBoardX, startBoardY;
440     } AnimState;
441
442 /* There can be two pieces being animated at once: a player
443    can begin dragging a piece before the remote opponent has moved. */
444
445 static AnimState game, player;
446
447 /* Bitmaps for use as masks when drawing XPM pieces.
448    Need one for each black and white piece.             */
449 static Pixmap xpmMask[BlackKing + 1];
450
451 /* This magic number is the number of intermediate frames used
452    in each half of the animation. For short moves it's reduced
453    by 1. The total number of frames will be factor * 2 + 1.  */
454 #define kFactor    4
455
456 SizeDefaults sizeDefaults[] = SIZE_DEFAULTS;
457
458 #define PAUSE_BUTTON "P"
459 MenuItem buttonBar[] = {
460     {"<<", "<<", ToStartEvent},
461     {"<", "<", BackwardEvent},
462     {N_(PAUSE_BUTTON), PAUSE_BUTTON, PauseEvent},
463     {">", ">", ForwardEvent},
464     {">>", ">>", ToEndEvent},
465     {NULL, NULL, NULL}
466 };
467
468 #define PIECE_MENU_SIZE 18
469 String pieceMenuStrings[2][PIECE_MENU_SIZE] = {
470     { N_("White"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
471       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
472       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
473       N_("Empty square"), N_("Clear board") },
474     { N_("Black"), "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"),
475       N_("Queen"), N_("King"), "----", N_("Elephant"), N_("Cannon"),
476       N_("Archbishop"), N_("Chancellor"), "----", N_("Promote"), N_("Demote"),
477       N_("Empty square"), N_("Clear board") }
478 };
479 /* must be in same order as pieceMenuStrings! */
480 ChessSquare pieceMenuTranslation[2][PIECE_MENU_SIZE] = {
481     { WhitePlay, (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
482         WhiteRook, WhiteQueen, WhiteKing, (ChessSquare) 0, WhiteAlfil,
483         WhiteCannon, WhiteAngel, WhiteMarshall, (ChessSquare) 0,
484         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
485     { BlackPlay, (ChessSquare) 0, BlackPawn, BlackKnight, BlackBishop,
486         BlackRook, BlackQueen, BlackKing, (ChessSquare) 0, BlackAlfil,
487         BlackCannon, BlackAngel, BlackMarshall, (ChessSquare) 0,
488         PromotePiece, DemotePiece, EmptySquare, ClearBoard },
489 };
490
491 #define DROP_MENU_SIZE 6
492 String dropMenuStrings[DROP_MENU_SIZE] = {
493     "----", N_("Pawn"), N_("Knight"), N_("Bishop"), N_("Rook"), N_("Queen")
494   };
495 /* must be in same order as dropMenuStrings! */
496 ChessSquare dropMenuTranslation[DROP_MENU_SIZE] = {
497     (ChessSquare) 0, WhitePawn, WhiteKnight, WhiteBishop,
498     WhiteRook, WhiteQueen
499 };
500
501 typedef struct {
502     char piece;
503     char* widget;
504 } DropMenuEnables;
505
506 DropMenuEnables dmEnables[] = {
507     { 'P', "Pawn" },
508     { 'N', "Knight" },
509     { 'B', "Bishop" },
510     { 'R', "Rook" },
511     { 'Q', "Queen" }
512 };
513
514 Arg shellArgs[] = {
515     { XtNwidth, 0 },
516     { XtNheight, 0 },
517     { XtNminWidth, 0 },
518     { XtNminHeight, 0 },
519     { XtNmaxWidth, 0 },
520     { XtNmaxHeight, 0 }
521 };
522
523 Arg layoutArgs[] = {
524     { XtNborderWidth, 0 },
525     { XtNdefaultDistance, 0 },
526 };
527
528 Arg formArgs[] = {
529     { XtNborderWidth, 0 },
530     { XtNresizable, (XtArgVal) True },
531 };
532
533 Arg boardArgs[] = {
534     { XtNborderWidth, 0 },
535     { XtNwidth, 0 },
536     { XtNheight, 0 }
537 };
538
539 Arg titleArgs[] = {
540     { XtNjustify, (XtArgVal) XtJustifyRight },
541     { XtNlabel, (XtArgVal) "..." },
542     { XtNresizable, (XtArgVal) True },
543     { XtNresize, (XtArgVal) False }
544 };
545
546 Arg messageArgs[] = {
547     { XtNjustify, (XtArgVal) XtJustifyLeft },
548     { XtNlabel, (XtArgVal) "..." },
549     { XtNresizable, (XtArgVal) True },
550     { XtNresize, (XtArgVal) False }
551 };
552
553 Arg timerArgs[] = {
554     { XtNborderWidth, 0 },
555     { XtNjustify, (XtArgVal) XtJustifyLeft }
556 };
557
558 XtResource clientResources[] = {
559     { "flashCount", "flashCount", XtRInt, sizeof(int),
560         XtOffset(AppDataPtr, flashCount), XtRImmediate,
561         (XtPointer) FLASH_COUNT  },
562 };
563
564 XrmOptionDescRec shellOptions[] = {
565     { "-flashCount", "flashCount", XrmoptionSepArg, NULL },
566     { "-flash", "flashCount", XrmoptionNoArg, "3" },
567     { "-xflash", "flashCount", XrmoptionNoArg, "0" },
568 };
569
570 XtActionsRec boardActions[] = {
571     { "DrawPosition", DrawPositionProc },
572     { "HandleUserMove", HandleUserMove },
573     { "AnimateUserMove", AnimateUserMove },
574     { "HandlePV", HandlePV },
575     { "SelectPV", SelectPV },
576     { "StopPV", StopPV },
577     { "FileNameAction", FileNameAction },
578     { "AskQuestionProc", AskQuestionProc },
579     { "AskQuestionReplyAction", AskQuestionReplyAction },
580     { "PieceMenuPopup", PieceMenuPopup },
581     { "WhiteClock", WhiteClock },
582     { "BlackClock", BlackClock },
583     { "MenuItem", KeyBindingProc }, // [HGM] generic handler for key bindings
584     { "QuitProc", QuitWrapper },
585     { "ManProc", ManInner },
586     { "TempBackwardProc", TempBackwardProc },
587     { "TempForwardProc", TempForwardProc },
588     { "CommentClick", (XtActionProc) CommentClick },
589     { "CommentPopDown", (XtActionProc) CommentPopDown },
590     { "TagsPopDown", (XtActionProc) TagsPopDown },
591     { "ErrorPopDown", (XtActionProc) ErrorPopDown },
592     { "ICSInputBoxPopDown", (XtActionProc) ICSInputBoxPopDown },
593     { "FileNamePopDown", (XtActionProc) FileNamePopDown },
594     { "AskQuestionPopDown", (XtActionProc) AskQuestionPopDown },
595     { "GameListPopDown", (XtActionProc) GameListPopDown },
596     { "GameListOptionsPopDown", (XtActionProc) GameListOptionsPopDown },
597     { "PromotionPopDown", (XtActionProc) PromotionPopDown },
598     { "EngineOutputPopDown", (XtActionProc) EngineOutputPopDown },
599     { "EvalGraphPopDown", (XtActionProc) EvalGraphPopDown },
600     { "GenericPopDown", (XtActionProc) GenericPopDown },
601     { "CopyMemoProc", (XtActionProc) CopyMemoProc },
602     { "SelectMove", (XtActionProc) SelectMove },
603     { "LoadSelectedProc", LoadSelectedProc },
604     { "SetFilterProc", SetFilterProc },
605     { "TypeInProc", TypeInProc },
606     { "EnterKeyProc", EnterKeyProc },
607     { "UpKeyProc", UpKeyProc },
608     { "DownKeyProc", DownKeyProc },
609 };
610
611 char globalTranslations[] =
612   ":<Key>F9: MenuItem(ResignProc) \n \
613    :Ctrl<Key>n: MenuItem(NewGame) \n \
614    :Meta<Key>V: MenuItem(NewVariant) \n \
615    :Ctrl<Key>o: MenuItem(LoadGame) \n \
616    :Meta<Key>Next: MenuItem(LoadNextGameProc) \n \
617    :Meta<Key>Prior: MenuItem(LoadPrevGameProc) \n \
618    :Ctrl<Key>Down: LoadSelectedProc(3) \n \
619    :Ctrl<Key>Up: LoadSelectedProc(-3) \n \
620    :Ctrl<Key>s: MenuItem(SaveGame) \n \
621    :Ctrl<Key>c: MenuItem(CopyGame) \n \
622    :Ctrl<Key>v: MenuItem(PasteGame) \n \
623    :Ctrl<Key>O: MenuItem(LoadPosition) \n \
624    :Shift<Key>Next: MenuItem(LoadNextPositionProc) \n \
625    :Shift<Key>Prior: MenuItem(LoadPrevPositionProc) \n \
626    :Ctrl<Key>S: MenuItem(SavePosition) \n \
627    :Ctrl<Key>C: MenuItem(CopyPosition) \n \
628    :Ctrl<Key>V: MenuItem(PastePosition) \n \
629    :Ctrl<Key>q: MenuItem(Exit) \n \
630    :Ctrl<Key>w: MenuItem(MachineWhite) \n \
631    :Ctrl<Key>b: MenuItem(MachineBlack) \n \
632    :Ctrl<Key>t: MenuItem(TwoMachines) \n \
633    :Ctrl<Key>a: MenuItem(AnalysisMode) \n \
634    :Ctrl<Key>g: MenuItem(AnalyzeFile) \n \
635    :Ctrl<Key>e: MenuItem(EditGame) \n \
636    :Ctrl<Key>E: MenuItem(EditPosition) \n \
637    :Meta<Key>O: MenuItem(ShowEngineOutput) \n \
638    :Meta<Key>E: MenuItem(ShowEvaluationGraph) \n \
639    :Meta<Key>G: MenuItem(ShowGameList) \n \
640    :Meta<Key>H: MenuItem(ShowMoveHistory) \n \
641    :<Key>Pause: MenuItem(Pause) \n \
642    :<Key>F3: MenuItem(Accept) \n \
643    :<Key>F4: MenuItem(Decline) \n \
644    :<Key>F12: MenuItem(Rematch) \n \
645    :<Key>F5: MenuItem(CallFlag) \n \
646    :<Key>F6: MenuItem(Draw) \n \
647    :<Key>F7: MenuItem(Adjourn) \n \
648    :<Key>F8: MenuItem(Abort) \n \
649    :<Key>F10: MenuItem(StopObserving) \n \
650    :<Key>F11: MenuItem(StopExamining) \n \
651    :Ctrl<Key>d: MenuItem(DebugProc) \n \
652    :Meta Ctrl<Key>F12: MenuItem(DebugProc) \n \
653    :Meta<Key>End: MenuItem(ToEnd) \n \
654    :Meta<Key>Right: MenuItem(Forward) \n \
655    :Meta<Key>Home: MenuItem(ToStart) \n \
656    :Meta<Key>Left: MenuItem(Backward) \n \
657    :<Key>Left: MenuItem(Backward) \n \
658    :<Key>Right: MenuItem(Forward) \n \
659    :<Key>Home: MenuItem(Revert) \n \
660    :<Key>End: MenuItem(TruncateGame) \n \
661    :Ctrl<Key>m: MenuItem(MoveNow) \n \
662    :Ctrl<Key>x: MenuItem(RetractMove) \n \
663    :Meta<Key>J: MenuItem(Adjudications) \n \
664    :Meta<Key>U: MenuItem(CommonEngine) \n \
665    :Meta<Key>T: MenuItem(TimeControl) \n \
666    :Ctrl<Key>P: MenuItem(PonderNextMove) \n "
667 #ifndef OPTIONSDIALOG
668     "\
669    :Ctrl<Key>Q: MenuItem(AlwaysQueenProc) \n \
670    :Ctrl<Key>F: MenuItem(AutoflagProc) \n \
671    :Ctrl<Key>A: MenuItem(AnimateMovingProc) \n \
672    :Ctrl<Key>L: MenuItem(TestLegalityProc) \n \
673    :Ctrl<Key>H: MenuItem(HideThinkingProc) \n "
674 #endif
675    "\
676    :<Key>F1: MenuItem(Manual) \n \
677    :<Key>F2: MenuItem(FlipView) \n \
678    :<KeyDown>Return: TempBackwardProc() \n \
679    :<KeyUp>Return: TempForwardProc() \n";
680
681 char boardTranslations[] =
682    "<Btn1Down>: HandleUserMove(0) \n \
683    Shift<Btn1Up>: HandleUserMove(1) \n \
684    <Btn1Up>: HandleUserMove(0) \n \
685    <Btn1Motion>: AnimateUserMove() \n \
686    <Btn3Motion>: HandlePV() \n \
687    <Btn2Motion>: HandlePV() \n \
688    <Btn3Up>: PieceMenuPopup(menuB) \n \
689    <Btn2Up>: PieceMenuPopup(menuB) \n \
690    Shift<Btn2Down>: XawPositionSimpleMenu(menuB) XawPositionSimpleMenu(menuD)\
691                  PieceMenuPopup(menuB) \n \
692    Any<Btn2Down>: XawPositionSimpleMenu(menuW) XawPositionSimpleMenu(menuD) \
693                  PieceMenuPopup(menuW) \n \
694    Shift<Btn3Down>: XawPositionSimpleMenu(menuW) XawPositionSimpleMenu(menuD)\
695                  PieceMenuPopup(menuW) \n \
696    Any<Btn3Down>: XawPositionSimpleMenu(menuB) XawPositionSimpleMenu(menuD) \
697                  PieceMenuPopup(menuB) \n";
698
699 char whiteTranslations[] =
700    "Shift<BtnDown>: WhiteClock(1)\n \
701    <BtnDown>: WhiteClock(0)\n";
702 char blackTranslations[] =
703    "Shift<BtnDown>: BlackClock(1)\n \
704    <BtnDown>: BlackClock(0)\n";
705
706 char ICSInputTranslations[] =
707     "<Key>Up: UpKeyProc() \n "
708     "<Key>Down: DownKeyProc() \n "
709     "<Key>Return: EnterKeyProc() \n";
710
711 // [HGM] vari: another hideous kludge: call extend-end first so we can be sure select-start works,
712 //             as the widget is destroyed before the up-click can call extend-end
713 char commentTranslations[] = "<Btn3Down>: extend-end() select-start() CommentClick() \n";
714
715 String xboardResources[] = {
716     "*fileName*value.translations: #override\\n <Key>Return: FileNameAction()",
717     "*question*value.translations: #override\\n <Key>Return: AskQuestionReplyAction()",
718     "*errorpopup*translations: #override\\n <Key>Return: ErrorPopDown()",
719     NULL
720   };
721
722
723 /* Max possible square size */
724 #define MAXSQSIZE 256
725
726 static int xpm_avail[MAXSQSIZE];
727
728 #ifdef HAVE_DIR_STRUCT
729
730 /* Extract piece size from filename */
731 static int
732 xpm_getsize (char *name, int len, char *ext)
733 {
734     char *p, *d;
735     char buf[10];
736
737     if (len < 4)
738       return 0;
739
740     if ((p=strchr(name, '.')) == NULL ||
741         StrCaseCmp(p+1, ext) != 0)
742       return 0;
743
744     p = name + 3;
745     d = buf;
746
747     while (*p && isdigit(*p))
748       *(d++) = *(p++);
749
750     *d = 0;
751     return atoi(buf);
752 }
753
754 /* Setup xpm_avail */
755 static int
756 xpm_getavail (char *dirname, char *ext)
757 {
758     DIR *dir;
759     struct dirent *ent;
760     int  i;
761
762     for (i=0; i<MAXSQSIZE; ++i)
763       xpm_avail[i] = 0;
764
765     if (appData.debugMode)
766       fprintf(stderr, "XPM dir:%s:ext:%s:\n", dirname, ext);
767
768     dir = opendir(dirname);
769     if (!dir)
770       {
771           fprintf(stderr, _("%s: Can't access XPM directory %s\n"),
772                   programName, dirname);
773           exit(1);
774       }
775
776     while ((ent=readdir(dir)) != NULL) {
777         i = xpm_getsize(ent->d_name, NAMLEN(ent), ext);
778         if (i > 0 && i < MAXSQSIZE)
779           xpm_avail[i] = 1;
780     }
781
782     closedir(dir);
783
784     return 0;
785 }
786
787 void
788 xpm_print_avail (FILE *fp, char *ext)
789 {
790     int i;
791
792     fprintf(fp, _("Available `%s' sizes:\n"), ext);
793     for (i=1; i<MAXSQSIZE; ++i) {
794         if (xpm_avail[i])
795           printf("%d\n", i);
796     }
797 }
798
799 /* Return XPM piecesize closest to size */
800 int
801 xpm_closest_to (char *dirname, int size, char *ext)
802 {
803     int i;
804     int sm_diff = MAXSQSIZE;
805     int sm_index = 0;
806     int diff;
807
808     xpm_getavail(dirname, ext);
809
810     if (appData.debugMode)
811       xpm_print_avail(stderr, ext);
812
813     for (i=1; i<MAXSQSIZE; ++i) {
814         if (xpm_avail[i]) {
815             diff = size - i;
816             diff = (diff<0) ? -diff : diff;
817             if (diff < sm_diff) {
818                 sm_diff = diff;
819                 sm_index = i;
820             }
821         }
822     }
823
824     if (!sm_index) {
825         fprintf(stderr, _("Error: No `%s' files!\n"), ext);
826         exit(1);
827     }
828
829     return sm_index;
830 }
831 #else   /* !HAVE_DIR_STRUCT */
832 /* If we are on a system without a DIR struct, we can't
833    read the directory, so we can't collect a list of
834    filenames, etc., so we can't do any size-fitting. */
835 int
836 xpm_closest_to (char *dirname, int size, char *ext)
837 {
838     fprintf(stderr, _("\
839 Warning: No DIR structure found on this system --\n\
840          Unable to autosize for XPM/XIM pieces.\n\
841    Please report this error to %s.\n\
842    Include system type & operating system in message.\n"), PACKAGE_BUGREPORT););
843     return size;
844 }
845 #endif /* HAVE_DIR_STRUCT */
846
847 static char *cnames[9] = { "black", "red", "green", "yellow", "blue",
848                              "magenta", "cyan", "white" };
849 typedef struct {
850     int attr, bg, fg;
851 } TextColors;
852 TextColors textColors[(int)NColorClasses];
853
854 /* String is: "fg, bg, attr". Which is 0, 1, 2 */
855 static int
856 parse_color (char *str, int which)
857 {
858     char *p, buf[100], *d;
859     int i;
860
861     if (strlen(str) > 99)       /* watch bounds on buf */
862       return -1;
863
864     p = str;
865     d = buf;
866     for (i=0; i<which; ++i) {
867         p = strchr(p, ',');
868         if (!p)
869           return -1;
870         ++p;
871     }
872
873     /* Could be looking at something like:
874        black, , 1
875        .. in which case we want to stop on a comma also */
876     while (*p && *p != ',' && !isalpha(*p) && !isdigit(*p))
877       ++p;
878
879     if (*p == ',') {
880         return -1;              /* Use default for empty field */
881     }
882
883     if (which == 2 || isdigit(*p))
884       return atoi(p);
885
886     while (*p && isalpha(*p))
887       *(d++) = *(p++);
888
889     *d = 0;
890
891     for (i=0; i<8; ++i) {
892         if (!StrCaseCmp(buf, cnames[i]))
893           return which? (i+40) : (i+30);
894     }
895     if (!StrCaseCmp(buf, "default")) return -1;
896
897     fprintf(stderr, _("%s: unrecognized color %s\n"), programName, buf);
898     return -2;
899 }
900
901 static int
902 parse_cpair (ColorClass cc, char *str)
903 {
904     if ((textColors[(int)cc].fg=parse_color(str, 0)) == -2) {
905         fprintf(stderr, _("%s: can't parse foreground color in `%s'\n"),
906                 programName, str);
907         return -1;
908     }
909
910     /* bg and attr are optional */
911     textColors[(int)cc].bg = parse_color(str, 1);
912     if ((textColors[(int)cc].attr = parse_color(str, 2)) < 0) {
913         textColors[(int)cc].attr = 0;
914     }
915     return 0;
916 }
917
918
919 /* Arrange to catch delete-window events */
920 Atom wm_delete_window;
921 void
922 CatchDeleteWindow (Widget w, String procname)
923 {
924   char buf[MSG_SIZ];
925   XSetWMProtocols(xDisplay, XtWindow(w), &wm_delete_window, 1);
926   snprintf(buf, sizeof(buf), "<Message>WM_PROTOCOLS: %s() \n", procname);
927   XtAugmentTranslations(w, XtParseTranslationTable(buf));
928 }
929
930 void
931 BoardToTop ()
932 {
933   Arg args[16];
934   XtSetArg(args[0], XtNiconic, False);
935   XtSetValues(shellWidget, args, 1);
936
937   XtPopup(shellWidget, XtGrabNone); /* Raise if lowered  */
938 }
939
940 //---------------------------------------------------------------------------------------------------------
941 // some symbol definitions to provide the proper (= XBoard) context for the code in args.h
942 #define XBOARD True
943 #define JAWS_ARGS
944 #define CW_USEDEFAULT (1<<31)
945 #define ICS_TEXT_MENU_SIZE 90
946 #define DEBUG_FILE "xboard.debug"
947 #define SetCurrentDirectory chdir
948 #define GetCurrentDirectory(SIZE, NAME) getcwd(NAME, SIZE)
949 #define OPTCHAR "-"
950 #define SEPCHAR " "
951
952 // these two must some day move to frontend.h, when they are implemented
953 Boolean GameListIsUp();
954
955 // The option definition and parsing code common to XBoard and WinBoard is collected in this file
956 #include "args.h"
957
958 // front-end part of option handling
959
960 // [HGM] This platform-dependent table provides the location for storing the color info
961 extern char *crWhite, * crBlack;
962
963 void *
964 colorVariable[] = {
965   &appData.whitePieceColor,
966   &appData.blackPieceColor,
967   &appData.lightSquareColor,
968   &appData.darkSquareColor,
969   &appData.highlightSquareColor,
970   &appData.premoveHighlightColor,
971   &appData.lowTimeWarningColor,
972   NULL,
973   NULL,
974   NULL,
975   NULL,
976   NULL,
977   &crWhite,
978   &crBlack,
979   NULL
980 };
981
982 // [HGM] font: keep a font for each square size, even non-stndard ones
983 #define NUM_SIZES 18
984 #define MAX_SIZE 130
985 Boolean fontIsSet[NUM_FONTS], fontValid[NUM_FONTS][MAX_SIZE];
986 char *fontTable[NUM_FONTS][MAX_SIZE];
987
988 void
989 ParseFont (char *name, int number)
990 { // in XBoard, only 2 of the fonts are currently implemented, and we just copy their name
991   int size;
992   if(sscanf(name, "size%d:", &size)) {
993     // [HGM] font: font is meant for specific boardSize (likely from settings file);
994     //       defer processing it until we know if it matches our board size
995     if(size >= 0 && size<MAX_SIZE) { // for now, fixed limit
996         fontTable[number][size] = strdup(strchr(name, ':')+1);
997         fontValid[number][size] = True;
998     }
999     return;
1000   }
1001   switch(number) {
1002     case 0: // CLOCK_FONT
1003         appData.clockFont = strdup(name);
1004       break;
1005     case 1: // MESSAGE_FONT
1006         appData.font = strdup(name);
1007       break;
1008     case 2: // COORD_FONT
1009         appData.coordFont = strdup(name);
1010       break;
1011     default:
1012       return;
1013   }
1014   fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
1015 }
1016
1017 void
1018 SetFontDefaults ()
1019 { // only 2 fonts currently
1020   appData.clockFont = CLOCK_FONT_NAME;
1021   appData.coordFont = COORD_FONT_NAME;
1022   appData.font  =   DEFAULT_FONT_NAME;
1023 }
1024
1025 void
1026 CreateFonts ()
1027 { // no-op, until we identify the code for this already in XBoard and move it here
1028 }
1029
1030 void
1031 ParseColor (int n, char *name)
1032 { // in XBoard, just copy the color-name string
1033   if(colorVariable[n]) *(char**)colorVariable[n] = strdup(name);
1034 }
1035
1036 void
1037 ParseTextAttribs (ColorClass cc, char *s)
1038 {
1039     (&appData.colorShout)[cc] = strdup(s);
1040 }
1041
1042 void
1043 ParseBoardSize (void *addr, char *name)
1044 {
1045     appData.boardSize = strdup(name);
1046 }
1047
1048 void
1049 LoadAllSounds ()
1050 { // In XBoard the sound-playing program takes care of obtaining the actual sound
1051 }
1052
1053 void
1054 SetCommPortDefaults ()
1055 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
1056 }
1057
1058 // [HGM] args: these three cases taken out to stay in front-end
1059 void
1060 SaveFontArg (FILE *f, ArgDescriptor *ad)
1061 {
1062   char *name;
1063   int i, n = (int)(intptr_t)ad->argLoc;
1064   switch(n) {
1065     case 0: // CLOCK_FONT
1066         name = appData.clockFont;
1067       break;
1068     case 1: // MESSAGE_FONT
1069         name = appData.font;
1070       break;
1071     case 2: // COORD_FONT
1072         name = appData.coordFont;
1073       break;
1074     default:
1075       return;
1076   }
1077   for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
1078     if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
1079         fontTable[n][squareSize] = strdup(name);
1080         fontValid[n][squareSize] = True;
1081         break;
1082   }
1083   for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
1084     fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
1085 }
1086
1087 void
1088 ExportSounds ()
1089 { // nothing to do, as the sounds are at all times represented by their text-string names already
1090 }
1091
1092 void
1093 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
1094 {       // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
1095         fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
1096 }
1097
1098 void
1099 SaveColor (FILE *f, ArgDescriptor *ad)
1100 {       // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
1101         if(colorVariable[(int)(intptr_t)ad->argLoc])
1102         fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
1103 }
1104
1105 void
1106 SaveBoardSize (FILE *f, char *name, void *addr)
1107 { // wrapper to shield back-end from BoardSize & sizeInfo
1108   fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
1109 }
1110
1111 void
1112 ParseCommPortSettings (char *s)
1113 { // no such option in XBoard (yet)
1114 }
1115
1116 extern Widget engineOutputShell;
1117 int frameX, frameY;
1118
1119 void
1120 GetActualPlacement (Widget wg, WindowPlacement *wp)
1121 {
1122   Arg args[16];
1123   Dimension w, h;
1124   Position x, y;
1125   XWindowAttributes winAt;
1126   Window win, dummy;
1127   int i, rx, ry;
1128
1129   if(!wg) return;
1130
1131     win = XtWindow(wg);
1132     XGetWindowAttributes(xDisplay, win, &winAt); // this works, where XtGetValues on XtNx, XtNy does not!
1133     XTranslateCoordinates (xDisplay, win, winAt.root, -winAt.border_width, -winAt.border_width, &rx, &ry, &dummy);
1134     wp->x = rx - winAt.x;
1135     wp->y = ry - winAt.y;
1136     wp->height = winAt.height;
1137     wp->width = winAt.width;
1138     frameX = winAt.x; frameY = winAt.y; // remember to decide if windows touch
1139 }
1140
1141 void
1142 GetWindowCoords ()
1143 { // wrapper to shield use of window handles from back-end (make addressible by number?)
1144   // In XBoard this will have to wait until awareness of window parameters is implemented
1145   GetActualPlacement(shellWidget, &wpMain);
1146   if(EngineOutputIsUp()) GetActualPlacement(engineOutputShell, &wpEngineOutput);
1147   if(MoveHistoryIsUp()) GetActualPlacement(shells[7], &wpMoveHistory);
1148   if(EvalGraphIsUp()) GetActualPlacement(evalGraphShell, &wpEvalGraph);
1149   if(GameListIsUp()) GetActualPlacement(gameListShell, &wpGameList);
1150   if(shellUp[1]) GetActualPlacement(shells[1], &wpComment);
1151   if(shellUp[2]) GetActualPlacement(shells[2], &wpTags);
1152 }
1153
1154 void
1155 PrintCommPortSettings (FILE *f, char *name)
1156 { // This option does not exist in XBoard
1157 }
1158
1159 int
1160 MySearchPath (char *installDir, char *name, char *fullname)
1161 { // just append installDir and name. Perhaps ExpandPath should be used here?
1162   name = ExpandPathName(name);
1163   if(name && name[0] == '/')
1164     safeStrCpy(fullname, name, MSG_SIZ );
1165   else {
1166     sprintf(fullname, "%s%c%s", installDir, '/', name);
1167   }
1168   return 1;
1169 }
1170
1171 int
1172 MyGetFullPathName (char *name, char *fullname)
1173 { // should use ExpandPath?
1174   name = ExpandPathName(name);
1175   safeStrCpy(fullname, name, MSG_SIZ );
1176   return 1;
1177 }
1178
1179 void
1180 EnsureOnScreen (int *x, int *y, int minX, int minY)
1181 {
1182   return;
1183 }
1184
1185 int
1186 MainWindowUp ()
1187 { // [HGM] args: allows testing if main window is realized from back-end
1188   return xBoardWindow != 0;
1189 }
1190
1191 void
1192 PopUpStartupDialog ()
1193 {  // start menu not implemented in XBoard
1194 }
1195
1196 char *
1197 ConvertToLine (int argc, char **argv)
1198 {
1199   static char line[128*1024], buf[1024];
1200   int i;
1201
1202   line[0] = NULLCHAR;
1203   for(i=1; i<argc; i++)
1204     {
1205       if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
1206           && argv[i][0] != '{' )
1207         snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
1208       else
1209         snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
1210       strncat(line, buf, 128*1024 - strlen(line) - 1 );
1211     }
1212
1213   line[strlen(line)-1] = NULLCHAR;
1214   return line;
1215 }
1216
1217 //--------------------------------------------------------------------------------------------
1218
1219 extern Boolean twoBoards, partnerUp;
1220
1221 #ifdef IDSIZES
1222   // eventually, all layout determining code should go into a subroutine, but until then IDSIZE remains undefined
1223 #else
1224 #define BoardSize int
1225 void
1226 InitDrawingSizes (BoardSize boardSize, int flags)
1227 {   // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
1228     Dimension timerWidth, boardWidth, boardHeight, w, h, sep, bor, wr, hr;
1229     Arg args[16];
1230     XtGeometryResult gres;
1231     int i;
1232     static Dimension oldWidth, oldHeight;
1233     static VariantClass oldVariant;
1234     static int oldDual = -1, oldMono = -1;
1235
1236     if(!formWidget) return;
1237
1238     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
1239     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
1240     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1241
1242   if(boardWidth != oldWidth || boardHeight != oldHeight || oldDual != twoBoards) { // do resizing stuff only if size actually changed
1243     /*
1244      * Enable shell resizing.
1245      */
1246     shellArgs[0].value = (XtArgVal) &w;
1247     shellArgs[1].value = (XtArgVal) &h;
1248     XtGetValues(shellWidget, shellArgs, 2);
1249
1250     shellArgs[4].value = 3*w; shellArgs[2].value = 10;
1251     shellArgs[5].value = 2*h; shellArgs[3].value = 10;
1252     XtSetValues(shellWidget, &shellArgs[2], 4);
1253
1254     XtSetArg(args[0], XtNdefaultDistance, &sep);
1255     XtGetValues(formWidget, args, 1);
1256
1257     oldWidth = boardWidth; oldHeight = boardHeight; oldDual = twoBoards;
1258     CreateGrid();
1259     hOffset = boardWidth + 10;
1260     for(i=0; i<BOARD_WIDTH+BOARD_HEIGHT+2; i++) { // [HGM] dual: grid for second board
1261         secondSegments[i] = gridSegments[i];
1262         secondSegments[i].x1 += hOffset;
1263         secondSegments[i].x2 += hOffset;
1264     }
1265
1266     XtSetArg(args[0], XtNwidth, boardWidth);
1267     XtSetArg(args[1], XtNheight, boardHeight);
1268     XtSetValues(boardWidget, args, 2);
1269
1270     timerWidth = (boardWidth - sep) / 2;
1271     XtSetArg(args[0], XtNwidth, timerWidth);
1272     XtSetValues(whiteTimerWidget, args, 1);
1273     XtSetValues(blackTimerWidget, args, 1);
1274
1275     XawFormDoLayout(formWidget, False);
1276
1277     if (appData.titleInWindow) {
1278         i = 0;
1279         XtSetArg(args[i], XtNborderWidth, &bor); i++;
1280         XtSetArg(args[i], XtNheight, &h);  i++;
1281         XtGetValues(titleWidget, args, i);
1282         if (smallLayout) {
1283             w = boardWidth - 2*bor;
1284         } else {
1285             XtSetArg(args[0], XtNwidth, &w);
1286             XtGetValues(menuBarWidget, args, 1);
1287             w = boardWidth - w - sep - 2*bor - 2; // WIDTH_FUDGE
1288         }
1289
1290         gres = XtMakeResizeRequest(titleWidget, w, h, &wr, &hr);
1291         if (gres != XtGeometryYes && appData.debugMode) {
1292             fprintf(stderr,
1293                     _("%s: titleWidget geometry error %d %d %d %d %d\n"),
1294                     programName, gres, w, h, wr, hr);
1295         }
1296     }
1297
1298     XawFormDoLayout(formWidget, True);
1299
1300     /*
1301      * Inhibit shell resizing.
1302      */
1303     shellArgs[0].value = w = (XtArgVal) boardWidth + marginW + twoBoards*hOffset; // [HGM] dual
1304     shellArgs[1].value = h = (XtArgVal) boardHeight + marginH;
1305     shellArgs[4].value = shellArgs[2].value = w;
1306     shellArgs[5].value = shellArgs[3].value = h;
1307     XtSetValues(shellWidget, &shellArgs[0], 6);
1308
1309     XSync(xDisplay, False);
1310     DelayedDrag();
1311   }
1312
1313     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
1314     // (only for xpm)
1315
1316   if(gameInfo.variant != oldVariant) { // and only if variant changed
1317
1318     if(useImages) {
1319       for(i=0; i<4; i++) {
1320         int p;
1321         for(p=0; p<=(int)WhiteKing; p++)
1322            xpmPieceBitmap[i][p] = xpmPieceBitmap2[i][p]; // defaults
1323         if(gameInfo.variant == VariantShogi) {
1324            xpmPieceBitmap[i][(int)WhiteCannon] = xpmPieceBitmap2[i][(int)WhiteKing+1];
1325            xpmPieceBitmap[i][(int)WhiteNightrider] = xpmPieceBitmap2[i][(int)WhiteKing+2];
1326            xpmPieceBitmap[i][(int)WhiteSilver] = xpmPieceBitmap2[i][(int)WhiteKing+3];
1327            xpmPieceBitmap[i][(int)WhiteGrasshopper] = xpmPieceBitmap2[i][(int)WhiteKing+4];
1328            xpmPieceBitmap[i][(int)WhiteQueen] = xpmPieceBitmap2[i][(int)WhiteLance];
1329         }
1330 #ifdef GOTHIC
1331         if(gameInfo.variant == VariantGothic) {
1332            xpmPieceBitmap[i][(int)WhiteMarshall] = xpmPieceBitmap2[i][(int)WhiteSilver];
1333         }
1334 #endif
1335         if(gameInfo.variant == VariantSChess && (squareSize == 49 || squareSize == 72)) {
1336            xpmPieceBitmap[i][(int)WhiteAngel]    = xpmPieceBitmap2[i][(int)WhiteFalcon];
1337            xpmPieceBitmap[i][(int)WhiteMarshall] = xpmPieceBitmap2[i][(int)WhiteAlfil];
1338         }
1339 #if !HAVE_LIBXPM
1340         // [HGM] why are thee ximMasks used at all? the ximPieceBitmaps seem to be never used!
1341         for(p=0; p<=(int)WhiteKing; p++)
1342            ximMaskPm[p] = ximMaskPm2[p]; // defaults
1343         if(gameInfo.variant == VariantShogi) {
1344            ximMaskPm[(int)WhiteCannon] = ximMaskPm2[(int)WhiteKing+1];
1345            ximMaskPm[(int)WhiteNightrider] = ximMaskPm2[(int)WhiteKing+2];
1346            ximMaskPm[(int)WhiteSilver] = ximMaskPm2[(int)WhiteKing+3];
1347            ximMaskPm[(int)WhiteGrasshopper] = ximMaskPm2[(int)WhiteKing+4];
1348            ximMaskPm[(int)WhiteQueen] = ximMaskPm2[(int)WhiteLance];
1349         }
1350 #ifdef GOTHIC
1351         if(gameInfo.variant == VariantGothic) {
1352            ximMaskPm[(int)WhiteMarshall] = ximMaskPm2[(int)WhiteSilver];
1353         }
1354 #endif
1355         if(gameInfo.variant == VariantSChess && (squareSize == 49 || squareSize == 72)) {
1356            ximMaskPm[(int)WhiteAngel]    = ximMaskPm2[(int)WhiteFalcon];
1357            ximMaskPm[(int)WhiteMarshall] = ximMaskPm2[(int)WhiteAlfil];
1358         }
1359 #endif
1360       }
1361     } else {
1362       for(i=0; i<2; i++) {
1363         int p;
1364         for(p=0; p<=(int)WhiteKing; p++)
1365            pieceBitmap[i][p] = pieceBitmap2[i][p]; // defaults
1366         if(gameInfo.variant == VariantShogi) {
1367            pieceBitmap[i][(int)WhiteCannon] = pieceBitmap2[i][(int)WhiteKing+1];
1368            pieceBitmap[i][(int)WhiteNightrider] = pieceBitmap2[i][(int)WhiteKing+2];
1369            pieceBitmap[i][(int)WhiteSilver] = pieceBitmap2[i][(int)WhiteKing+3];
1370            pieceBitmap[i][(int)WhiteGrasshopper] = pieceBitmap2[i][(int)WhiteKing+4];
1371            pieceBitmap[i][(int)WhiteQueen] = pieceBitmap2[i][(int)WhiteLance];
1372         }
1373 #ifdef GOTHIC
1374         if(gameInfo.variant == VariantGothic) {
1375            pieceBitmap[i][(int)WhiteMarshall] = pieceBitmap2[i][(int)WhiteSilver];
1376         }
1377 #endif
1378         if(gameInfo.variant == VariantSChess && (squareSize == 49 || squareSize == 72)) {
1379            pieceBitmap[i][(int)WhiteAngel]    = pieceBitmap2[i][(int)WhiteFalcon];
1380            pieceBitmap[i][(int)WhiteMarshall] = pieceBitmap2[i][(int)WhiteAlfil];
1381         }
1382       }
1383     }
1384     oldMono = -10; // kludge to force recreation of animation masks
1385     oldVariant = gameInfo.variant;
1386   }
1387 #if HAVE_LIBXPM
1388   if(appData.monoMode != oldMono)
1389     CreateAnimVars();
1390 #endif
1391   oldMono = appData.monoMode;
1392 }
1393 #endif
1394
1395 void
1396 ParseIcsTextColors ()
1397 {   // [HGM] tken out of main(), so it can be called from ICS-Options dialog
1398     if (parse_cpair(ColorShout, appData.colorShout) < 0 ||
1399         parse_cpair(ColorSShout, appData.colorSShout) < 0 ||
1400         parse_cpair(ColorChannel1, appData.colorChannel1) < 0  ||
1401         parse_cpair(ColorChannel, appData.colorChannel) < 0  ||
1402         parse_cpair(ColorKibitz, appData.colorKibitz) < 0 ||
1403         parse_cpair(ColorTell, appData.colorTell) < 0 ||
1404         parse_cpair(ColorChallenge, appData.colorChallenge) < 0  ||
1405         parse_cpair(ColorRequest, appData.colorRequest) < 0  ||
1406         parse_cpair(ColorSeek, appData.colorSeek) < 0  ||
1407         parse_cpair(ColorNormal, appData.colorNormal) < 0)
1408       {
1409           if (appData.colorize) {
1410               fprintf(stderr,
1411                       _("%s: can't parse color names; disabling colorization\n"),
1412                       programName);
1413           }
1414           appData.colorize = FALSE;
1415       }
1416 }
1417
1418 int
1419 MakeColors ()
1420 {   // [HGM] taken out of main(), so it can be called from BoardOptions dialog
1421     XrmValue vFrom, vTo;
1422     int forceMono = False;
1423
1424     if (!appData.monoMode) {
1425         vFrom.addr = (caddr_t) appData.lightSquareColor;
1426         vFrom.size = strlen(appData.lightSquareColor);
1427         XtConvert(shellWidget, XtRString, &vFrom, XtRPixel, &vTo);
1428         if (vTo.addr == NULL) {
1429           appData.monoMode = True;
1430           forceMono = True;
1431         } else {
1432           lightSquareColor = *(Pixel *) vTo.addr;
1433         }
1434     }
1435     if (!appData.monoMode) {
1436         vFrom.addr = (caddr_t) appData.darkSquareColor;
1437         vFrom.size = strlen(appData.darkSquareColor);
1438         XtConvert(shellWidget, XtRString, &vFrom, XtRPixel, &vTo);
1439         if (vTo.addr == NULL) {
1440           appData.monoMode = True;
1441           forceMono = True;
1442         } else {
1443           darkSquareColor = *(Pixel *) vTo.addr;
1444         }
1445     }
1446     if (!appData.monoMode) {
1447         vFrom.addr = (caddr_t) appData.whitePieceColor;
1448         vFrom.size = strlen(appData.whitePieceColor);
1449         XtConvert(shellWidget, XtRString, &vFrom, XtRPixel, &vTo);
1450         if (vTo.addr == NULL) {
1451           appData.monoMode = True;
1452           forceMono = True;
1453         } else {
1454           whitePieceColor = *(Pixel *) vTo.addr;
1455         }
1456     }
1457     if (!appData.monoMode) {
1458         vFrom.addr = (caddr_t) appData.blackPieceColor;
1459         vFrom.size = strlen(appData.blackPieceColor);
1460         XtConvert(shellWidget, XtRString, &vFrom, XtRPixel, &vTo);
1461         if (vTo.addr == NULL) {
1462           appData.monoMode = True;
1463           forceMono = True;
1464         } else {
1465           blackPieceColor = *(Pixel *) vTo.addr;
1466         }
1467     }
1468
1469     if (!appData.monoMode) {
1470         vFrom.addr = (caddr_t) appData.highlightSquareColor;
1471         vFrom.size = strlen(appData.highlightSquareColor);
1472         XtConvert(shellWidget, XtRString, &vFrom, XtRPixel, &vTo);
1473         if (vTo.addr == NULL) {
1474           appData.monoMode = True;
1475           forceMono = True;
1476         } else {
1477           highlightSquareColor = *(Pixel *) vTo.addr;
1478         }
1479     }
1480
1481     if (!appData.monoMode) {
1482         vFrom.addr = (caddr_t) appData.premoveHighlightColor;
1483         vFrom.size = strlen(appData.premoveHighlightColor);
1484         XtConvert(shellWidget, XtRString, &vFrom, XtRPixel, &vTo);
1485         if (vTo.addr == NULL) {
1486           appData.monoMode = True;
1487           forceMono = True;
1488         } else {
1489           premoveHighlightColor = *(Pixel *) vTo.addr;
1490         }
1491     }
1492     return forceMono;
1493 }
1494
1495 void
1496 CreateAnyPieces ()
1497 {   // [HGM] taken out of main
1498 #if HAVE_LIBXPM
1499     if (appData.monoMode && // [HGM] no sense to go on to certain doom
1500        (appData.bitmapDirectory == NULL || appData.bitmapDirectory[0] == NULLCHAR))
1501             appData.bitmapDirectory = strdup(DEF_BITMAP_DIR);
1502
1503     if (appData.bitmapDirectory[0] != NULLCHAR) {
1504       CreatePieces();
1505     } else {
1506       CreateXPMPieces();
1507       CreateXPMBoard(appData.liteBackTextureFile, 1);
1508       CreateXPMBoard(appData.darkBackTextureFile, 0);
1509     }
1510 #else
1511     CreateXIMPieces();
1512     /* Create regular pieces */
1513     if (!useImages) CreatePieces();
1514 #endif
1515 }
1516
1517 int
1518 main (int argc, char **argv)
1519 {
1520     int i, j, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
1521     XSetWindowAttributes window_attributes;
1522     Arg args[16];
1523     Dimension timerWidth, boardWidth, boardHeight, w, h, sep, bor, wr, hr;
1524     XrmValue vFrom, vTo;
1525     XtGeometryResult gres;
1526     char *p;
1527     XrmDatabase xdb;
1528     int forceMono = False;
1529
1530     srandom(time(0)); // [HGM] book: make random truly random
1531
1532     setbuf(stdout, NULL);
1533     setbuf(stderr, NULL);
1534     debugFP = stderr;
1535
1536     if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
1537         printf("%s version %s\n", PACKAGE_NAME, PACKAGE_VERSION);
1538         exit(0);
1539     }
1540
1541     programName = strrchr(argv[0], '/');
1542     if (programName == NULL)
1543       programName = argv[0];
1544     else
1545       programName++;
1546
1547 #ifdef ENABLE_NLS
1548     XtSetLanguageProc(NULL, NULL, NULL);
1549     bindtextdomain(PACKAGE, LOCALEDIR);
1550     textdomain(PACKAGE);
1551 #endif
1552
1553     shellWidget =
1554       XtAppInitialize(&appContext, "XBoard", shellOptions,
1555                       XtNumber(shellOptions),
1556                       &argc, argv, xboardResources, NULL, 0);
1557     appData.boardSize = "";
1558     InitAppData(ConvertToLine(argc, argv));
1559     p = getenv("HOME");
1560     if (p == NULL) p = "/tmp";
1561     i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
1562     gameCopyFilename = (char*) malloc(i);
1563     gamePasteFilename = (char*) malloc(i);
1564     snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
1565     snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
1566
1567     XtGetApplicationResources(shellWidget, (XtPointer) &appData,
1568                               clientResources, XtNumber(clientResources),
1569                               NULL, 0);
1570
1571     { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
1572         static char buf[MSG_SIZ];
1573         EscapeExpand(buf, appData.firstInitString);
1574         appData.firstInitString = strdup(buf);
1575         EscapeExpand(buf, appData.secondInitString);
1576         appData.secondInitString = strdup(buf);
1577         EscapeExpand(buf, appData.firstComputerString);
1578         appData.firstComputerString = strdup(buf);
1579         EscapeExpand(buf, appData.secondComputerString);
1580         appData.secondComputerString = strdup(buf);
1581     }
1582
1583     if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
1584         chessDir = ".";
1585     } else {
1586         if (chdir(chessDir) != 0) {
1587             fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
1588             perror(chessDir);
1589             exit(1);
1590         }
1591     }
1592
1593     if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
1594         /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
1595         if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL)  {
1596            printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
1597            exit(errno);
1598         }
1599         setbuf(debugFP, NULL);
1600     }
1601
1602 #if ENABLE_NLS
1603     if (appData.debugMode) {
1604       fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
1605     }
1606 #endif
1607
1608     /* [HGM,HR] make sure board size is acceptable */
1609     if(appData.NrFiles > BOARD_FILES ||
1610        appData.NrRanks > BOARD_RANKS   )
1611          DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
1612
1613 #if !HIGHDRAG
1614     /* This feature does not work; animation needs a rewrite */
1615     appData.highlightDragging = FALSE;
1616 #endif
1617     InitBackEnd1();
1618
1619     xDisplay = XtDisplay(shellWidget);
1620     xScreen = DefaultScreen(xDisplay);
1621     wm_delete_window = XInternAtom(xDisplay, "WM_DELETE_WINDOW", True);
1622
1623         gameInfo.variant = StringToVariant(appData.variant);
1624         InitPosition(FALSE);
1625
1626 #ifdef IDSIZE
1627     InitDrawingSizes(-1, 0); // [HGM] initsize: make this into a subroutine
1628 #else
1629     if (isdigit(appData.boardSize[0])) {
1630         i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
1631                    &lineGap, &clockFontPxlSize, &coordFontPxlSize,
1632                    &fontPxlSize, &smallLayout, &tinyLayout);
1633         if (i == 0) {
1634             fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
1635                     programName, appData.boardSize);
1636             exit(2);
1637         }
1638         if (i < 7) {
1639             /* Find some defaults; use the nearest known size */
1640             SizeDefaults *szd, *nearest;
1641             int distance = 99999;
1642             nearest = szd = sizeDefaults;
1643             while (szd->name != NULL) {
1644                 if (abs(szd->squareSize - squareSize) < distance) {
1645                     nearest = szd;
1646                     distance = abs(szd->squareSize - squareSize);
1647                     if (distance == 0) break;
1648                 }
1649                 szd++;
1650             }
1651             if (i < 2) lineGap = nearest->lineGap;
1652             if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
1653             if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
1654             if (i < 5) fontPxlSize = nearest->fontPxlSize;
1655             if (i < 6) smallLayout = nearest->smallLayout;
1656             if (i < 7) tinyLayout = nearest->tinyLayout;
1657         }
1658     } else {
1659         SizeDefaults *szd = sizeDefaults;
1660         if (*appData.boardSize == NULLCHAR) {
1661             while (DisplayWidth(xDisplay, xScreen) < szd->minScreenSize ||
1662                    DisplayHeight(xDisplay, xScreen) < szd->minScreenSize) {
1663               szd++;
1664             }
1665             if (szd->name == NULL) szd--;
1666             appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
1667         } else {
1668             while (szd->name != NULL &&
1669                    StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
1670             if (szd->name == NULL) {
1671                 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
1672                         programName, appData.boardSize);
1673                 exit(2);
1674             }
1675         }
1676         squareSize = szd->squareSize;
1677         lineGap = szd->lineGap;
1678         clockFontPxlSize = szd->clockFontPxlSize;
1679         coordFontPxlSize = szd->coordFontPxlSize;
1680         fontPxlSize = szd->fontPxlSize;
1681         smallLayout = szd->smallLayout;
1682         tinyLayout = szd->tinyLayout;
1683         // [HGM] font: use defaults from settings file if available and not overruled
1684     }
1685     if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
1686         appData.clockFont = fontTable[CLOCK_FONT][squareSize];
1687     if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
1688         appData.font = fontTable[MESSAGE_FONT][squareSize];
1689     if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
1690         appData.coordFont = fontTable[COORD_FONT][squareSize];
1691
1692     /* Now, using squareSize as a hint, find a good XPM/XIM set size */
1693     if (strlen(appData.pixmapDirectory) > 0) {
1694         p = ExpandPathName(appData.pixmapDirectory);
1695         if (!p) {
1696             fprintf(stderr, _("Error expanding path name \"%s\"\n"),
1697                    appData.pixmapDirectory);
1698             exit(1);
1699         }
1700         if (appData.debugMode) {
1701           fprintf(stderr, _("\
1702 XBoard square size (hint): %d\n\
1703 %s fulldir:%s:\n"), squareSize, IMAGE_EXT, p);
1704         }
1705         squareSize = xpm_closest_to(p, squareSize, IMAGE_EXT);
1706         if (appData.debugMode) {
1707             fprintf(stderr, _("Closest %s size: %d\n"), IMAGE_EXT, squareSize);
1708         }
1709     }
1710     defaultLineGap = lineGap;
1711     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
1712
1713     /* [HR] height treated separately (hacked) */
1714     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
1715     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1716     if (appData.showJail == 1) {
1717         /* Jail on top and bottom */
1718         XtSetArg(boardArgs[1], XtNwidth, boardWidth);
1719         XtSetArg(boardArgs[2], XtNheight,
1720                  boardHeight + 2*(lineGap + squareSize));
1721     } else if (appData.showJail == 2) {
1722         /* Jail on sides */
1723         XtSetArg(boardArgs[1], XtNwidth,
1724                  boardWidth + 2*(lineGap + squareSize));
1725         XtSetArg(boardArgs[2], XtNheight, boardHeight);
1726     } else {
1727         /* No jail */
1728         XtSetArg(boardArgs[1], XtNwidth, boardWidth);
1729         XtSetArg(boardArgs[2], XtNheight, boardHeight);
1730     }
1731
1732     /*
1733      * Determine what fonts to use.
1734      */
1735 #if ENABLE_NLS
1736     appData.font = InsertPxlSize(appData.font, fontPxlSize);
1737     appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
1738     appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
1739     fontSet = CreateFontSet(appData.font);
1740     clockFontSet = CreateFontSet(appData.clockFont);
1741     {
1742       /* For the coordFont, use the 0th font of the fontset. */
1743       XFontSet coordFontSet = CreateFontSet(appData.coordFont);
1744       XFontStruct **font_struct_list;
1745       XFontSetExtents *fontSize;
1746       char **font_name_list;
1747       XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
1748       coordFontID = XLoadFont(xDisplay, font_name_list[0]);
1749       coordFontStruct = XQueryFont(xDisplay, coordFontID);
1750       fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
1751       textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
1752     }
1753 #else
1754     appData.font = FindFont(appData.font, fontPxlSize);
1755     appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
1756     appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
1757     clockFontID = XLoadFont(xDisplay, appData.clockFont);
1758     clockFontStruct = XQueryFont(xDisplay, clockFontID);
1759     coordFontID = XLoadFont(xDisplay, appData.coordFont);
1760     coordFontStruct = XQueryFont(xDisplay, coordFontID);
1761 #endif
1762     countFontID = coordFontID;  // [HGM] holdings
1763     countFontStruct = coordFontStruct;
1764
1765     xdb = XtDatabase(xDisplay);
1766 #if ENABLE_NLS
1767     XrmPutLineResource(&xdb, "*international: True");
1768     vTo.size = sizeof(XFontSet);
1769     vTo.addr = (XtPointer) &fontSet;
1770     XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
1771 #else
1772     XrmPutStringResource(&xdb, "*font", appData.font);
1773 #endif
1774
1775     /*
1776      * Detect if there are not enough colors available and adapt.
1777      */
1778     if (DefaultDepth(xDisplay, xScreen) <= 2) {
1779       appData.monoMode = True;
1780     }
1781
1782     forceMono = MakeColors();
1783
1784     if (forceMono) {
1785       fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1786               programName);
1787         appData.monoMode = True;
1788     }
1789
1790     if (appData.lowTimeWarning && !appData.monoMode) {
1791       vFrom.addr = (caddr_t) appData.lowTimeWarningColor;
1792       vFrom.size = strlen(appData.lowTimeWarningColor);
1793       XtConvert(shellWidget, XtRString, &vFrom, XtRPixel, &vTo);
1794       if (vTo.addr == NULL)
1795                 appData.monoMode = True;
1796       else
1797                 lowTimeWarningColor = *(Pixel *) vTo.addr;
1798     }
1799
1800     if (appData.monoMode && appData.debugMode) {
1801         fprintf(stderr, _("white pixel = 0x%lx, black pixel = 0x%lx\n"),
1802                 (unsigned long) XWhitePixel(xDisplay, xScreen),
1803                 (unsigned long) XBlackPixel(xDisplay, xScreen));
1804     }
1805
1806     ParseIcsTextColors();
1807     textColors[ColorNone].fg = textColors[ColorNone].bg = -1;
1808     textColors[ColorNone].attr = 0;
1809
1810     XtAppAddActions(appContext, boardActions, XtNumber(boardActions));
1811
1812     /*
1813      * widget hierarchy
1814      */
1815     if (tinyLayout) {
1816         layoutName = "tinyLayout";
1817     } else if (smallLayout) {
1818         layoutName = "smallLayout";
1819     } else {
1820         layoutName = "normalLayout";
1821     }
1822     /* Outer layoutWidget is there only to provide a name for use in
1823        resources that depend on the layout style */
1824     layoutWidget =
1825       XtCreateManagedWidget(layoutName, formWidgetClass, shellWidget,
1826                             layoutArgs, XtNumber(layoutArgs));
1827     formWidget =
1828       XtCreateManagedWidget("form", formWidgetClass, layoutWidget,
1829                             formArgs, XtNumber(formArgs));
1830     XtSetArg(args[0], XtNdefaultDistance, &sep);
1831     XtGetValues(formWidget, args, 1);
1832
1833     j = 0;
1834     widgetList[j++] = menuBarWidget = CreateMenuBar(menuBar, boardWidth);
1835     XtSetArg(args[0], XtNtop,    XtChainTop);
1836     XtSetArg(args[1], XtNbottom, XtChainTop);
1837     XtSetArg(args[2], XtNright,  XtChainLeft);
1838     XtSetValues(menuBarWidget, args, 3);
1839
1840     widgetList[j++] = whiteTimerWidget =
1841       XtCreateWidget("whiteTime", labelWidgetClass,
1842                      formWidget, timerArgs, XtNumber(timerArgs));
1843 #if ENABLE_NLS
1844     XtSetArg(args[0], XtNfontSet, clockFontSet);
1845 #else
1846     XtSetArg(args[0], XtNfont, clockFontStruct);
1847 #endif
1848     XtSetArg(args[1], XtNtop,    XtChainTop);
1849     XtSetArg(args[2], XtNbottom, XtChainTop);
1850     XtSetValues(whiteTimerWidget, args, 3);
1851
1852     widgetList[j++] = blackTimerWidget =
1853       XtCreateWidget("blackTime", labelWidgetClass,
1854                      formWidget, timerArgs, XtNumber(timerArgs));
1855 #if ENABLE_NLS
1856     XtSetArg(args[0], XtNfontSet, clockFontSet);
1857 #else
1858     XtSetArg(args[0], XtNfont, clockFontStruct);
1859 #endif
1860     XtSetArg(args[1], XtNtop,    XtChainTop);
1861     XtSetArg(args[2], XtNbottom, XtChainTop);
1862     XtSetValues(blackTimerWidget, args, 3);
1863
1864     if (appData.titleInWindow) {
1865         widgetList[j++] = titleWidget =
1866           XtCreateWidget("title", labelWidgetClass, formWidget,
1867                          titleArgs, XtNumber(titleArgs));
1868         XtSetArg(args[0], XtNtop,    XtChainTop);
1869         XtSetArg(args[1], XtNbottom, XtChainTop);
1870         XtSetValues(titleWidget, args, 2);
1871     }
1872
1873     if (appData.showButtonBar) {
1874       widgetList[j++] = buttonBarWidget = CreateButtonBar(buttonBar);
1875       XtSetArg(args[0], XtNleft,  XtChainRight); // [HGM] glue to right window edge
1876       XtSetArg(args[1], XtNright, XtChainRight); //       for good run-time sizing
1877       XtSetArg(args[2], XtNtop,    XtChainTop);
1878       XtSetArg(args[3], XtNbottom, XtChainTop);
1879       XtSetValues(buttonBarWidget, args, 4);
1880     }
1881
1882     widgetList[j++] = messageWidget =
1883       XtCreateWidget("message", labelWidgetClass, formWidget,
1884                      messageArgs, XtNumber(messageArgs));
1885     XtSetArg(args[0], XtNtop,    XtChainTop);
1886     XtSetArg(args[1], XtNbottom, XtChainTop);
1887     XtSetValues(messageWidget, args, 2);
1888
1889     widgetList[j++] = boardWidget =
1890       XtCreateWidget("board", widgetClass, formWidget, boardArgs,
1891                      XtNumber(boardArgs));
1892
1893     XtManageChildren(widgetList, j);
1894
1895     timerWidth = (boardWidth - sep) / 2;
1896     XtSetArg(args[0], XtNwidth, timerWidth);
1897     XtSetValues(whiteTimerWidget, args, 1);
1898     XtSetValues(blackTimerWidget, args, 1);
1899
1900     XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1901     XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1902     XtGetValues(whiteTimerWidget, args, 2);
1903
1904     if (appData.showButtonBar) {
1905       XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1906       XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1907       XtGetValues(XtNameToWidget(buttonBarWidget, PAUSE_BUTTON), args, 2);
1908     }
1909
1910     /*
1911      * formWidget uses these constraints but they are stored
1912      * in the children.
1913      */
1914     i = 0;
1915     XtSetArg(args[i], XtNfromHoriz, 0); i++;
1916     XtSetValues(menuBarWidget, args, i);
1917     if (appData.titleInWindow) {
1918         if (smallLayout) {
1919             i = 0;
1920             XtSetArg(args[i], XtNfromVert, menuBarWidget); i++;
1921             XtSetValues(whiteTimerWidget, args, i);
1922             i = 0;
1923             XtSetArg(args[i], XtNfromVert, menuBarWidget); i++;
1924             XtSetArg(args[i], XtNfromHoriz, whiteTimerWidget); i++;
1925             XtSetValues(blackTimerWidget, args, i);
1926             i = 0;
1927             XtSetArg(args[i], XtNfromVert, whiteTimerWidget); i++;
1928             XtSetArg(args[i], XtNjustify, XtJustifyLeft); i++;
1929             XtSetValues(titleWidget, args, i);
1930             i = 0;
1931             XtSetArg(args[i], XtNfromVert, titleWidget); i++;
1932             XtSetArg(args[i], XtNresizable, (XtArgVal) True); i++;
1933             XtSetValues(messageWidget, args, i);
1934             if (appData.showButtonBar) {
1935               i = 0;
1936               XtSetArg(args[i], XtNfromVert, titleWidget); i++;
1937               XtSetArg(args[i], XtNfromHoriz, messageWidget); i++;
1938               XtSetValues(buttonBarWidget, args, i);
1939             }
1940         } else {
1941             i = 0;
1942             XtSetArg(args[i], XtNfromVert, titleWidget); i++;
1943             XtSetValues(whiteTimerWidget, args, i);
1944             i = 0;
1945             XtSetArg(args[i], XtNfromVert, titleWidget); i++;
1946             XtSetArg(args[i], XtNfromHoriz, whiteTimerWidget); i++;
1947             XtSetValues(blackTimerWidget, args, i);
1948             i = 0;
1949             XtSetArg(args[i], XtNfromHoriz, menuBarWidget); i++;
1950             XtSetValues(titleWidget, args, i);
1951             i = 0;
1952             XtSetArg(args[i], XtNfromVert, whiteTimerWidget); i++;
1953             XtSetArg(args[i], XtNresizable, (XtArgVal) True); i++;
1954             XtSetValues(messageWidget, args, i);
1955             if (appData.showButtonBar) {
1956               i = 0;
1957               XtSetArg(args[i], XtNfromVert, whiteTimerWidget); i++;
1958               XtSetArg(args[i], XtNfromHoriz, messageWidget); i++;
1959               XtSetValues(buttonBarWidget, args, i);
1960             }
1961         }
1962     } else {
1963         i = 0;
1964         XtSetArg(args[i], XtNfromVert, menuBarWidget); i++;
1965         XtSetValues(whiteTimerWidget, args, i);
1966         i = 0;
1967         XtSetArg(args[i], XtNfromVert, menuBarWidget); i++;
1968         XtSetArg(args[i], XtNfromHoriz, whiteTimerWidget); i++;
1969         XtSetValues(blackTimerWidget, args, i);
1970         i = 0;
1971         XtSetArg(args[i], XtNfromVert, whiteTimerWidget); i++;
1972         XtSetArg(args[i], XtNresizable, (XtArgVal) True); i++;
1973         XtSetValues(messageWidget, args, i);
1974         if (appData.showButtonBar) {
1975           i = 0;
1976           XtSetArg(args[i], XtNfromVert, whiteTimerWidget); i++;
1977           XtSetArg(args[i], XtNfromHoriz, messageWidget); i++;
1978           XtSetValues(buttonBarWidget, args, i);
1979         }
1980     }
1981     i = 0;
1982     XtSetArg(args[0], XtNfromVert, messageWidget);
1983     XtSetArg(args[1], XtNtop,    XtChainTop);
1984     XtSetArg(args[2], XtNbottom, XtChainBottom);
1985     XtSetArg(args[3], XtNleft,   XtChainLeft);
1986     XtSetArg(args[4], XtNright,  XtChainRight);
1987     XtSetValues(boardWidget, args, 5);
1988
1989     XtRealizeWidget(shellWidget);
1990
1991     if(wpMain.x > 0) {
1992       XtSetArg(args[0], XtNx, wpMain.x);
1993       XtSetArg(args[1], XtNy, wpMain.y);
1994       XtSetValues(shellWidget, args, 2);
1995     }
1996
1997     /*
1998      * Correct the width of the message and title widgets.
1999      * It is not known why some systems need the extra fudge term.
2000      * The value "2" is probably larger than needed.
2001      */
2002     XawFormDoLayout(formWidget, False);
2003
2004 #define WIDTH_FUDGE 2
2005     i = 0;
2006     XtSetArg(args[i], XtNborderWidth, &bor);  i++;
2007     XtSetArg(args[i], XtNheight, &h);  i++;
2008     XtGetValues(messageWidget, args, i);
2009     if (appData.showButtonBar) {
2010       i = 0;
2011       XtSetArg(args[i], XtNwidth, &w);  i++;
2012       XtGetValues(buttonBarWidget, args, i);
2013       w = boardWidth - w - sep - 2*bor - WIDTH_FUDGE;
2014     } else {
2015       w = boardWidth - 2*bor + 1; /*!! +1 compensates for kludge below */
2016     }
2017
2018     gres = XtMakeResizeRequest(messageWidget, w, h, &wr, &hr);
2019     if (gres != XtGeometryYes && appData.debugMode) {
2020       fprintf(stderr, _("%s: messageWidget geometry error %d %d %d %d %d\n"),
2021               programName, gres, w, h, wr, hr);
2022     }
2023
2024     /* !! Horrible hack to work around bug in XFree86 4.0.1 (X11R6.4.3) */
2025     /* The size used for the child widget in layout lags one resize behind
2026        its true size, so we resize a second time, 1 pixel smaller.  Yeech! */
2027     w--;
2028     gres = XtMakeResizeRequest(messageWidget, w, h, &wr, &hr);
2029     if (gres != XtGeometryYes && appData.debugMode) {
2030       fprintf(stderr, _("%s: messageWidget geometry error %d %d %d %d %d\n"),
2031               programName, gres, w, h, wr, hr);
2032     }
2033     /* !! end hack */
2034     if(!textHeight) textHeight = hr; // [HGM] if !NLS textHeight is still undefined, and we grab it from here
2035     XtSetArg(args[0], XtNleft,  XtChainLeft);  // [HGM] glue ends for good run-time sizing
2036     XtSetArg(args[1], XtNright, XtChainRight);
2037     XtSetValues(messageWidget, args, 2);
2038
2039     if (appData.titleInWindow) {
2040         i = 0;
2041         XtSetArg(args[i], XtNborderWidth, &bor); i++;
2042         XtSetArg(args[i], XtNheight, &h);  i++;
2043         XtGetValues(titleWidget, args, i);
2044         if (smallLayout) {
2045             w = boardWidth - 2*bor;
2046         } else {
2047             XtSetArg(args[0], XtNwidth, &w);
2048             XtGetValues(menuBarWidget, args, 1);
2049             w = boardWidth - w - sep - 2*bor - WIDTH_FUDGE;
2050         }
2051
2052         gres = XtMakeResizeRequest(titleWidget, w, h, &wr, &hr);
2053         if (gres != XtGeometryYes && appData.debugMode) {
2054             fprintf(stderr,
2055                     _("%s: titleWidget geometry error %d %d %d %d %d\n"),
2056                     programName, gres, w, h, wr, hr);
2057         }
2058     }
2059     XawFormDoLayout(formWidget, True);
2060
2061     xBoardWindow = XtWindow(boardWidget);
2062
2063     // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
2064     //       not need to go into InitDrawingSizes().
2065 #endif
2066
2067     /*
2068      * Create X checkmark bitmap and initialize option menu checks.
2069      */
2070     ReadBitmap(&xMarkPixmap, "checkmark.bm",
2071                checkmark_bits, checkmark_width, checkmark_height);
2072     XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
2073 #ifndef OPTIONSDIALOG
2074     if (appData.alwaysPromoteToQueen) {
2075         XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Always Queen"),
2076                     args, 1);
2077     }
2078     if (appData.animateDragging) {
2079         XtSetValues(XtNameToWidget(menuBarWidget,
2080                                    "menuOptions.Animate Dragging"),
2081                     args, 1);
2082     }
2083     if (appData.animate) {
2084         XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Animate Moving"),
2085                     args, 1);
2086     }
2087     if (appData.autoCallFlag) {
2088         XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Auto Flag"),
2089                     args, 1);
2090     }
2091     if (appData.autoFlipView) {
2092         XtSetValues(XtNameToWidget(menuBarWidget,"menuOptions.Auto Flip View"),
2093                     args, 1);
2094     }
2095     if (appData.blindfold) {
2096         XtSetValues(XtNameToWidget(menuBarWidget,
2097                                    "menuOptions.Blindfold"), args, 1);
2098     }
2099     if (appData.flashCount > 0) {
2100         XtSetValues(XtNameToWidget(menuBarWidget,
2101                                    "menuOptions.Flash Moves"),
2102                     args, 1);
2103     }
2104 #if HIGHDRAG
2105     if (appData.highlightDragging) {
2106         XtSetValues(XtNameToWidget(menuBarWidget,
2107                                    "menuOptions.Highlight Dragging"),
2108                     args, 1);
2109     }
2110 #endif
2111     if (appData.highlightLastMove) {
2112         XtSetValues(XtNameToWidget(menuBarWidget,
2113                                    "menuOptions.Highlight Last Move"),
2114                     args, 1);
2115     }
2116     if (appData.highlightMoveWithArrow) {
2117         XtSetValues(XtNameToWidget(menuBarWidget,
2118                                    "menuOptions.Arrow"),
2119                     args, 1);
2120     }
2121 //    if (appData.icsAlarm) {
2122 //      XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.ICS Alarm"),
2123 //                  args, 1);
2124 //    }
2125     if (appData.ringBellAfterMoves) {
2126         XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Move Sound"),
2127                     args, 1);
2128     }
2129     if (appData.oneClick) {
2130         XtSetValues(XtNameToWidget(menuBarWidget,
2131                                    "menuOptions.OneClick"), args, 1);
2132     }
2133     if (appData.periodicUpdates) {
2134         XtSetValues(XtNameToWidget(menuBarWidget,
2135                                    "menuOptions.Periodic Updates"), args, 1);
2136     }
2137     if (appData.ponderNextMove) {
2138         XtSetValues(XtNameToWidget(menuBarWidget,
2139                                    "menuOptions.Ponder Next Move"), args, 1);
2140     }
2141     if (appData.popupExitMessage) {
2142         XtSetValues(XtNameToWidget(menuBarWidget,
2143                                    "menuOptions.Popup Exit Message"), args, 1);
2144     }
2145     if (appData.popupMoveErrors) {
2146         XtSetValues(XtNameToWidget(menuBarWidget,
2147                                    "menuOptions.Popup Move Errors"), args, 1);
2148     }
2149 //    if (appData.premove) {
2150 //      XtSetValues(XtNameToWidget(menuBarWidget,
2151 //                                 "menuOptions.Premove"), args, 1);
2152 //    }
2153     if (appData.showCoords) {
2154         XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Show Coords"),
2155                     args, 1);
2156     }
2157     if (appData.hideThinkingFromHuman) {
2158         XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Hide Thinking"),
2159                     args, 1);
2160     }
2161     if (appData.testLegality) {
2162         XtSetValues(XtNameToWidget(menuBarWidget,"menuOptions.Test Legality"),
2163                     args, 1);
2164     }
2165 #endif
2166     if (saveSettingsOnExit) {
2167         XtSetValues(XtNameToWidget(menuBarWidget,"menuOptions.Save Settings on Exit"),
2168                     args, 1);
2169     }
2170
2171     /*
2172      * Create an icon.
2173      */
2174     ReadBitmap(&wIconPixmap, "icon_white.bm",
2175                icon_white_bits, icon_white_width, icon_white_height);
2176     ReadBitmap(&bIconPixmap, "icon_black.bm",
2177                icon_black_bits, icon_black_width, icon_black_height);
2178     iconPixmap = wIconPixmap;
2179     i = 0;
2180     XtSetArg(args[i], XtNiconPixmap, iconPixmap);  i++;
2181     XtSetValues(shellWidget, args, i);
2182
2183     /*
2184      * Create a cursor for the board widget.
2185      */
2186     window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
2187     XChangeWindowAttributes(xDisplay, xBoardWindow,
2188                             CWCursor, &window_attributes);
2189
2190     /*
2191      * Inhibit shell resizing.
2192      */
2193     shellArgs[0].value = (XtArgVal) &w;
2194     shellArgs[1].value = (XtArgVal) &h;
2195     XtGetValues(shellWidget, shellArgs, 2);
2196     shellArgs[4].value = shellArgs[2].value = w;
2197     shellArgs[5].value = shellArgs[3].value = h;
2198     XtSetValues(shellWidget, &shellArgs[2], 4);
2199     marginW =  w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
2200     marginH =  h - boardHeight;
2201
2202     CatchDeleteWindow(shellWidget, "QuitProc");
2203
2204     CreateGCs(False);
2205     CreateGrid();
2206     CreateAnyPieces();
2207
2208     CreatePieceMenus();
2209
2210     if (appData.animate || appData.animateDragging)
2211       CreateAnimVars();
2212
2213     XtAugmentTranslations(formWidget,
2214                           XtParseTranslationTable(globalTranslations));
2215     XtAugmentTranslations(boardWidget,
2216                           XtParseTranslationTable(boardTranslations));
2217     XtAugmentTranslations(whiteTimerWidget,
2218                           XtParseTranslationTable(whiteTranslations));
2219     XtAugmentTranslations(blackTimerWidget,
2220                           XtParseTranslationTable(blackTranslations));
2221
2222     /* Why is the following needed on some versions of X instead
2223      * of a translation? */
2224     XtAddEventHandler(boardWidget, ExposureMask|PointerMotionMask, False,
2225                       (XtEventHandler) EventProc, NULL);
2226     /* end why */
2227     XtAddEventHandler(formWidget, KeyPressMask, False,
2228                       (XtEventHandler) MoveTypeInProc, NULL);
2229     XtAddEventHandler(shellWidget, StructureNotifyMask, False,
2230                       (XtEventHandler) EventProc, NULL);
2231
2232     /* [AS] Restore layout */
2233     if( wpMoveHistory.visible ) {
2234       HistoryPopUp();
2235     }
2236
2237     if( wpEvalGraph.visible )
2238       {
2239         EvalGraphPopUp();
2240       };
2241
2242     if( wpEngineOutput.visible ) {
2243       EngineOutputPopUp();
2244     }
2245
2246     InitBackEnd2();
2247
2248     if (errorExitStatus == -1) {
2249         if (appData.icsActive) {
2250             /* We now wait until we see "login:" from the ICS before
2251                sending the logon script (problems with timestamp otherwise) */
2252             /*ICSInitScript();*/
2253             if (appData.icsInputBox) ICSInputBoxPopUp();
2254         }
2255
2256     #ifdef SIGWINCH
2257     signal(SIGWINCH, TermSizeSigHandler);
2258     #endif
2259         signal(SIGINT, IntSigHandler);
2260         signal(SIGTERM, IntSigHandler);
2261         if (*appData.cmailGameName != NULLCHAR) {
2262             signal(SIGUSR1, CmailSigHandler);
2263         }
2264     }
2265     gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
2266     InitPosition(TRUE);
2267 //    XtSetKeyboardFocus(shellWidget, formWidget);
2268     XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
2269
2270     XtAppMainLoop(appContext);
2271     if (appData.debugMode) fclose(debugFP); // [DM] debug
2272     return 0;
2273 }
2274
2275 static Boolean noEcho;
2276
2277 void
2278 ShutDownFrontEnd ()
2279 {
2280     if (appData.icsActive && oldICSInteractionTitle != NULL) {
2281         DisplayIcsInteractionTitle(oldICSInteractionTitle);
2282     }
2283     if (saveSettingsOnExit) SaveSettings(settingsFileName);
2284     unlink(gameCopyFilename);
2285     unlink(gamePasteFilename);
2286     if(noEcho) EchoOn();
2287 }
2288
2289 RETSIGTYPE
2290 TermSizeSigHandler (int sig)
2291 {
2292     update_ics_width();
2293 }
2294
2295 RETSIGTYPE
2296 IntSigHandler (int sig)
2297 {
2298     ExitEvent(sig);
2299 }
2300
2301 RETSIGTYPE
2302 CmailSigHandler (int sig)
2303 {
2304     int dummy = 0;
2305     int error;
2306
2307     signal(SIGUSR1, SIG_IGN);   /* suspend handler     */
2308
2309     /* Activate call-back function CmailSigHandlerCallBack()             */
2310     OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
2311
2312     signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
2313 }
2314
2315 void
2316 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
2317 {
2318     BoardToTop();
2319     ReloadCmailMsgEvent(TRUE);  /* Reload cmail msg  */
2320 }
2321 /**** end signal code ****/
2322
2323
2324 void
2325 ICSInitScript ()
2326 {
2327   /* try to open the icsLogon script, either in the location given
2328    * or in the users HOME directory
2329    */
2330
2331   FILE *f;
2332   char buf[MSG_SIZ];
2333   char *homedir;
2334
2335   f = fopen(appData.icsLogon, "r");
2336   if (f == NULL)
2337     {
2338       homedir = getenv("HOME");
2339       if (homedir != NULL)
2340         {
2341           safeStrCpy(buf, homedir, sizeof(buf)/sizeof(buf[0]) );
2342           strncat(buf, "/", MSG_SIZ - strlen(buf) - 1);
2343           strncat(buf, appData.icsLogon,  MSG_SIZ - strlen(buf) - 1);
2344           f = fopen(buf, "r");
2345         }
2346     }
2347
2348   if (f != NULL)
2349     ProcessICSInitScript(f);
2350   else
2351     printf("Warning: Couldn't open icsLogon file (checked %s and %s).\n", appData.icsLogon, buf);
2352
2353   return;
2354 }
2355
2356 void
2357 ResetFrontEnd ()
2358 {
2359     CommentPopDown();
2360     TagsPopDown();
2361     return;
2362 }
2363
2364 typedef struct {
2365     char *name;
2366     Boolean value;
2367 } Enables;
2368
2369 void
2370 GreyRevert (Boolean grey)
2371 {
2372     Widget w;
2373     if (!menuBarWidget) return;
2374     w = XtNameToWidget(menuBarWidget, "menuEdit.Revert");
2375     if (w == NULL) {
2376       DisplayError("menuEdit.Revert", 0);
2377     } else {
2378       XtSetSensitive(w, !grey);
2379     }
2380     w = XtNameToWidget(menuBarWidget, "menuEdit.Annotate");
2381     if (w == NULL) {
2382       DisplayError("menuEdit.Annotate", 0);
2383     } else {
2384       XtSetSensitive(w, !grey);
2385     }
2386 }
2387
2388 void
2389 SetMenuEnables (Enables *enab)
2390 {
2391   Widget w;
2392   if (!menuBarWidget) return;
2393   while (enab->name != NULL) {
2394     w = XtNameToWidget(menuBarWidget, enab->name);
2395     if (w == NULL) {
2396       DisplayError(enab->name, 0);
2397     } else {
2398       XtSetSensitive(w, enab->value);
2399     }
2400     enab++;
2401   }
2402 }
2403
2404 Enables icsEnables[] = {
2405     { "menuFile.Mail Move", False },
2406     { "menuFile.Reload CMail Message", False },
2407     { "menuMode.Machine Black", False },
2408     { "menuMode.Machine White", False },
2409     { "menuMode.Analysis Mode", False },
2410     { "menuMode.Analyze File", False },
2411     { "menuMode.Two Machines", False },
2412     { "menuMode.Machine Match", False },
2413 #ifndef ZIPPY
2414     { "menuEngine.Hint", False },
2415     { "menuEngine.Book", False },
2416     { "menuEngine.Move Now", False },
2417 #ifndef OPTIONSDIALOG
2418     { "menuOptions.Periodic Updates", False },
2419     { "menuOptions.Hide Thinking", False },
2420     { "menuOptions.Ponder Next Move", False },
2421 #endif
2422 #endif
2423     { "menuEngine.Engine #1 Settings", False },
2424     { "menuEngine.Engine #2 Settings", False },
2425     { "menuEngine.Load Engine", False },
2426     { "menuEdit.Annotate", False },
2427     { "menuOptions.Match", False },
2428     { NULL, False }
2429 };
2430
2431 Enables ncpEnables[] = {
2432     { "menuFile.Mail Move", False },
2433     { "menuFile.Reload CMail Message", False },
2434     { "menuMode.Machine White", False },
2435     { "menuMode.Machine Black", False },
2436     { "menuMode.Analysis Mode", False },
2437     { "menuMode.Analyze File", False },
2438     { "menuMode.Two Machines", False },
2439     { "menuMode.Machine Match", False },
2440     { "menuMode.ICS Client", False },
2441     { "menuView.ICStex", False },
2442     { "menuView.ICS Input Box", False },
2443     { "Action", False },
2444     { "menuEdit.Revert", False },
2445     { "menuEdit.Annotate", False },
2446     { "menuEngine.Engine #1 Settings", False },
2447     { "menuEngine.Engine #2 Settings", False },
2448     { "menuEngine.Move Now", False },
2449     { "menuEngine.Retract Move", False },
2450     { "menuOptions.ICS", False },
2451 #ifndef OPTIONSDIALOG
2452     { "menuOptions.Auto Flag", False },
2453     { "menuOptions.Auto Flip View", False },
2454 //    { "menuOptions.ICS Alarm", False },
2455     { "menuOptions.Move Sound", False },
2456     { "menuOptions.Hide Thinking", False },
2457     { "menuOptions.Periodic Updates", False },
2458     { "menuOptions.Ponder Next Move", False },
2459 #endif
2460     { "menuEngine.Hint", False },
2461     { "menuEngine.Book", False },
2462     { NULL, False }
2463 };
2464
2465 Enables gnuEnables[] = {
2466     { "menuMode.ICS Client", False },
2467     { "menuView.ICStex", False },
2468     { "menuView.ICS Input Box", False },
2469     { "menuAction.Accept", False },
2470     { "menuAction.Decline", False },
2471     { "menuAction.Rematch", False },
2472     { "menuAction.Adjourn", False },
2473     { "menuAction.Stop Examining", False },
2474     { "menuAction.Stop Observing", False },
2475     { "menuAction.Upload to Examine", False },
2476     { "menuEdit.Revert", False },
2477     { "menuEdit.Annotate", False },
2478     { "menuOptions.ICS", False },
2479
2480     /* The next two options rely on SetCmailMode being called *after*    */
2481     /* SetGNUMode so that when GNU is being used to give hints these     */
2482     /* menu options are still available                                  */
2483
2484     { "menuFile.Mail Move", False },
2485     { "menuFile.Reload CMail Message", False },
2486     // [HGM] The following have been added to make a switch from ncp to GNU mode possible
2487     { "menuMode.Machine White", True },
2488     { "menuMode.Machine Black", True },
2489     { "menuMode.Analysis Mode", True },
2490     { "menuMode.Analyze File", True },
2491     { "menuMode.Two Machines", True },
2492     { "menuMode.Machine Match", True },
2493     { "menuEngine.Engine #1 Settings", True },
2494     { "menuEngine.Engine #2 Settings", True },
2495     { "menuEngine.Hint", True },
2496     { "menuEngine.Book", True },
2497     { "menuEngine.Move Now", True },
2498     { "menuEngine.Retract Move", True },
2499     { "Action", True },
2500     { NULL, False }
2501 };
2502
2503 Enables cmailEnables[] = {
2504     { "Action", True },
2505     { "menuAction.Call Flag", False },
2506     { "menuAction.Draw", True },
2507     { "menuAction.Adjourn", False },
2508     { "menuAction.Abort", False },
2509     { "menuAction.Stop Observing", False },
2510     { "menuAction.Stop Examining", False },
2511     { "menuFile.Mail Move", True },
2512     { "menuFile.Reload CMail Message", True },
2513     { NULL, False }
2514 };
2515
2516 Enables trainingOnEnables[] = {
2517   { "menuMode.Edit Comment", False },
2518   { "menuMode.Pause", False },
2519   { "menuEdit.Forward", False },
2520   { "menuEdit.Backward", False },
2521   { "menuEdit.Forward to End", False },
2522   { "menuEdit.Back to Start", False },
2523   { "menuEngine.Move Now", False },
2524   { "menuEdit.Truncate Game", False },
2525   { NULL, False }
2526 };
2527
2528 Enables trainingOffEnables[] = {
2529   { "menuMode.Edit Comment", True },
2530   { "menuMode.Pause", True },
2531   { "menuEdit.Forward", True },
2532   { "menuEdit.Backward", True },
2533   { "menuEdit.Forward to End", True },
2534   { "menuEdit.Back to Start", True },
2535   { "menuEngine.Move Now", True },
2536   { "menuEdit.Truncate Game", True },
2537   { NULL, False }
2538 };
2539
2540 Enables machineThinkingEnables[] = {
2541   { "menuFile.Load Game", False },
2542 //  { "menuFile.Load Next Game", False },
2543 //  { "menuFile.Load Previous Game", False },
2544 //  { "menuFile.Reload Same Game", False },
2545   { "menuEdit.Paste Game", False },
2546   { "menuFile.Load Position", False },
2547 //  { "menuFile.Load Next Position", False },
2548 //  { "menuFile.Load Previous Position", False },
2549 //  { "menuFile.Reload Same Position", False },
2550   { "menuEdit.Paste Position", False },
2551   { "menuMode.Machine White", False },
2552   { "menuMode.Machine Black", False },
2553   { "menuMode.Two Machines", False },
2554 //  { "menuMode.Machine Match", False },
2555   { "menuEngine.Retract Move", False },
2556   { NULL, False }
2557 };
2558
2559 Enables userThinkingEnables[] = {
2560   { "menuFile.Load Game", True },
2561 //  { "menuFile.Load Next Game", True },
2562 //  { "menuFile.Load Previous Game", True },
2563 //  { "menuFile.Reload Same Game", True },
2564   { "menuEdit.Paste Game", True },
2565   { "menuFile.Load Position", True },
2566 //  { "menuFile.Load Next Position", True },
2567 //  { "menuFile.Load Previous Position", True },
2568 //  { "menuFile.Reload Same Position", True },
2569   { "menuEdit.Paste Position", True },
2570   { "menuMode.Machine White", True },
2571   { "menuMode.Machine Black", True },
2572   { "menuMode.Two Machines", True },
2573 //  { "menuMode.Machine Match", True },
2574   { "menuEngine.Retract Move", True },
2575   { NULL, False }
2576 };
2577
2578 void
2579 SetICSMode ()
2580 {
2581   SetMenuEnables(icsEnables);
2582
2583 #if ZIPPY
2584   if (appData.zippyPlay && !appData.noChessProgram) { /* [DM] icsEngineAnalyze */
2585      XtSetSensitive(XtNameToWidget(menuBarWidget, "menuMode.Analysis Mode"), True);
2586      XtSetSensitive(XtNameToWidget(menuBarWidget, "menuEngine.Engine #1 Settings"), True);
2587   }
2588 #endif
2589 }
2590
2591 void
2592 SetNCPMode ()
2593 {
2594   SetMenuEnables(ncpEnables);
2595 }
2596
2597 void
2598 SetGNUMode ()
2599 {
2600   SetMenuEnables(gnuEnables);
2601 }
2602
2603 void
2604 SetCmailMode ()
2605 {
2606   SetMenuEnables(cmailEnables);
2607 }
2608
2609 void
2610 SetTrainingModeOn ()
2611 {
2612   SetMenuEnables(trainingOnEnables);
2613   if (appData.showButtonBar) {
2614     XtSetSensitive(buttonBarWidget, False);
2615   }
2616   CommentPopDown();
2617 }
2618
2619 void
2620 SetTrainingModeOff ()
2621 {
2622   SetMenuEnables(trainingOffEnables);
2623   if (appData.showButtonBar) {
2624     XtSetSensitive(buttonBarWidget, True);
2625   }
2626 }
2627
2628 void
2629 SetUserThinkingEnables ()
2630 {
2631   if (appData.noChessProgram) return;
2632   SetMenuEnables(userThinkingEnables);
2633 }
2634
2635 void
2636 SetMachineThinkingEnables ()
2637 {
2638   if (appData.noChessProgram) return;
2639   SetMenuEnables(machineThinkingEnables);
2640   switch (gameMode) {
2641   case MachinePlaysBlack:
2642   case MachinePlaysWhite:
2643   case TwoMachinesPlay:
2644     XtSetSensitive(XtNameToWidget(menuBarWidget,
2645                                   ModeToWidgetName(gameMode)), True);
2646     break;
2647   default:
2648     break;
2649   }
2650 }
2651
2652 // [HGM] code borrowed from winboard.c (which should thus go to backend.c!)
2653 #define HISTORY_SIZE 64
2654 static char *history[HISTORY_SIZE];
2655 int histIn = 0, histP = 0;
2656
2657 void
2658 SaveInHistory (char *cmd)
2659 {
2660   if (history[histIn] != NULL) {
2661     free(history[histIn]);
2662     history[histIn] = NULL;
2663   }
2664   if (*cmd == NULLCHAR) return;
2665   history[histIn] = StrSave(cmd);
2666   histIn = (histIn + 1) % HISTORY_SIZE;
2667   if (history[histIn] != NULL) {
2668     free(history[histIn]);
2669     history[histIn] = NULL;
2670   }
2671   histP = histIn;
2672 }
2673
2674 char *
2675 PrevInHistory (char *cmd)
2676 {
2677   int newhp;
2678   if (histP == histIn) {
2679     if (history[histIn] != NULL) free(history[histIn]);
2680     history[histIn] = StrSave(cmd);
2681   }
2682   newhp = (histP - 1 + HISTORY_SIZE) % HISTORY_SIZE;
2683   if (newhp == histIn || history[newhp] == NULL) return NULL;
2684   histP = newhp;
2685   return history[histP];
2686 }
2687
2688 char *
2689 NextInHistory ()
2690 {
2691   if (histP == histIn) return NULL;
2692   histP = (histP + 1) % HISTORY_SIZE;
2693   return history[histP];   
2694 }
2695 // end of borrowed code
2696
2697 #define Abs(n) ((n)<0 ? -(n) : (n))
2698
2699 #ifdef ENABLE_NLS
2700 char *
2701 InsertPxlSize (char *pattern, int targetPxlSize)
2702 {
2703     char *base_fnt_lst, strInt[12], *p, *q;
2704     int alternatives, i, len, strIntLen;
2705
2706     /*
2707      * Replace the "*" (if present) in the pixel-size slot of each
2708      * alternative with the targetPxlSize.
2709      */
2710     p = pattern;
2711     alternatives = 1;
2712     while ((p = strchr(p, ',')) != NULL) {
2713       alternatives++;
2714       p++;
2715     }
2716     snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
2717     strIntLen = strlen(strInt);
2718     base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
2719
2720     p = pattern;
2721     q = base_fnt_lst;
2722     while (alternatives--) {
2723       char *comma = strchr(p, ',');
2724       for (i=0; i<14; i++) {
2725         char *hyphen = strchr(p, '-');
2726         if (!hyphen) break;
2727         if (comma && hyphen > comma) break;
2728         len = hyphen + 1 - p;
2729         if (i == 7 && *p == '*' && len == 2) {
2730           p += len;
2731           memcpy(q, strInt, strIntLen);
2732           q += strIntLen;
2733           *q++ = '-';
2734         } else {
2735           memcpy(q, p, len);
2736           p += len;
2737           q += len;
2738         }
2739       }
2740       if (!comma) break;
2741       len = comma + 1 - p;
2742       memcpy(q, p, len);
2743       p += len;
2744       q += len;
2745     }
2746     strcpy(q, p);
2747
2748     return base_fnt_lst;
2749 }
2750
2751 XFontSet
2752 CreateFontSet (char *base_fnt_lst)
2753 {
2754     XFontSet fntSet;
2755     char **missing_list;
2756     int missing_count;
2757     char *def_string;
2758
2759     fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
2760                             &missing_list, &missing_count, &def_string);
2761     if (appData.debugMode) {
2762       int i, count;
2763       XFontStruct **font_struct_list;
2764       char **font_name_list;
2765       fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
2766       if (fntSet) {
2767         fprintf(debugFP, " got list %s, locale %s\n",
2768                 XBaseFontNameListOfFontSet(fntSet),
2769                 XLocaleOfFontSet(fntSet));
2770         count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
2771         for (i = 0; i < count; i++) {
2772           fprintf(debugFP, " got charset %s\n", font_name_list[i]);
2773         }
2774       }
2775       for (i = 0; i < missing_count; i++) {
2776         fprintf(debugFP, " missing charset %s\n", missing_list[i]);
2777       }
2778     }
2779     if (fntSet == NULL) {
2780       fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
2781       exit(2);
2782     }
2783     return fntSet;
2784 }
2785 #else // not ENABLE_NLS
2786 /*
2787  * Find a font that matches "pattern" that is as close as
2788  * possible to the targetPxlSize.  Prefer fonts that are k
2789  * pixels smaller to fonts that are k pixels larger.  The
2790  * pattern must be in the X Consortium standard format,
2791  * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
2792  * The return value should be freed with XtFree when no
2793  * longer needed.
2794  */
2795 char *
2796 FindFont (char *pattern, int targetPxlSize)
2797 {
2798     char **fonts, *p, *best, *scalable, *scalableTail;
2799     int i, j, nfonts, minerr, err, pxlSize;
2800
2801     fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
2802     if (nfonts < 1) {
2803         fprintf(stderr, _("%s: no fonts match pattern %s\n"),
2804                 programName, pattern);
2805         exit(2);
2806     }
2807
2808     best = fonts[0];
2809     scalable = NULL;
2810     minerr = 999999;
2811     for (i=0; i<nfonts; i++) {
2812         j = 0;
2813         p = fonts[i];
2814         if (*p != '-') continue;
2815         while (j < 7) {
2816             if (*p == NULLCHAR) break;
2817             if (*p++ == '-') j++;
2818         }
2819         if (j < 7) continue;
2820         pxlSize = atoi(p);
2821         if (pxlSize == 0) {
2822             scalable = fonts[i];
2823             scalableTail = p;
2824         } else {
2825             err = pxlSize - targetPxlSize;
2826             if (Abs(err) < Abs(minerr) ||
2827                 (minerr > 0 && err < 0 && -err == minerr)) {
2828                 best = fonts[i];
2829                 minerr = err;
2830             }
2831         }
2832     }
2833     if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
2834         /* If the error is too big and there is a scalable font,
2835            use the scalable font. */
2836         int headlen = scalableTail - scalable;
2837         p = (char *) XtMalloc(strlen(scalable) + 10);
2838         while (isdigit(*scalableTail)) scalableTail++;
2839         sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
2840     } else {
2841         p = (char *) XtMalloc(strlen(best) + 2);
2842         safeStrCpy(p, best, strlen(best)+1 );
2843     }
2844     if (appData.debugMode) {
2845         fprintf(debugFP, _("resolved %s at pixel size %d\n  to %s\n"),
2846                 pattern, targetPxlSize, p);
2847     }
2848     XFreeFontNames(fonts);
2849     return p;
2850 }
2851 #endif
2852
2853 void
2854 DeleteGCs ()
2855 {   // [HGM] deletes GCs that are to be remade, to prevent resource leak;
2856     // must be called before all non-first callse to CreateGCs()
2857     XtReleaseGC(shellWidget, highlineGC);
2858     XtReleaseGC(shellWidget, lightSquareGC);
2859     XtReleaseGC(shellWidget, darkSquareGC);
2860     XtReleaseGC(shellWidget, lineGC);
2861     if (appData.monoMode) {
2862         if (DefaultDepth(xDisplay, xScreen) == 1) {
2863             XtReleaseGC(shellWidget, wbPieceGC);
2864         } else {
2865             XtReleaseGC(shellWidget, bwPieceGC);
2866         }
2867     } else {
2868         XtReleaseGC(shellWidget, prelineGC);
2869         XtReleaseGC(shellWidget, jailSquareGC);
2870         XtReleaseGC(shellWidget, wdPieceGC);
2871         XtReleaseGC(shellWidget, wlPieceGC);
2872         XtReleaseGC(shellWidget, wjPieceGC);
2873         XtReleaseGC(shellWidget, bdPieceGC);
2874         XtReleaseGC(shellWidget, blPieceGC);
2875         XtReleaseGC(shellWidget, bjPieceGC);
2876     }
2877 }
2878
2879 void
2880 CreateGCs (int redo)
2881 {
2882     XtGCMask value_mask = GCLineWidth | GCLineStyle | GCForeground
2883       | GCBackground | GCFunction | GCPlaneMask;
2884     XGCValues gc_values;
2885     GC copyInvertedGC;
2886
2887     gc_values.plane_mask = AllPlanes;
2888     gc_values.line_width = lineGap;
2889     gc_values.line_style = LineSolid;
2890     gc_values.function = GXcopy;
2891
2892   if(redo) {
2893     DeleteGCs(); // called a second time; clean up old GCs first
2894   } else { // [HGM] grid and font GCs created on first call only
2895     gc_values.foreground = XBlackPixel(xDisplay, xScreen);
2896     gc_values.background = XWhitePixel(xDisplay, xScreen);
2897     coordGC = XtGetGC(shellWidget, value_mask, &gc_values);
2898     XSetFont(xDisplay, coordGC, coordFontID);
2899
2900     // [HGM] make font for holdings counts (white on black)
2901     gc_values.foreground = XWhitePixel(xDisplay, xScreen);
2902     gc_values.background = XBlackPixel(xDisplay, xScreen);
2903     countGC = XtGetGC(shellWidget, value_mask, &gc_values);
2904     XSetFont(xDisplay, countGC, countFontID);
2905   }
2906     gc_values.foreground = XBlackPixel(xDisplay, xScreen);
2907     gc_values.background = XBlackPixel(xDisplay, xScreen);
2908     lineGC = XtGetGC(shellWidget, value_mask, &gc_values);
2909
2910     if (appData.monoMode) {
2911         gc_values.foreground = XWhitePixel(xDisplay, xScreen);
2912         gc_values.background = XWhitePixel(xDisplay, xScreen);
2913         highlineGC = XtGetGC(shellWidget, value_mask, &gc_values);
2914
2915         gc_values.foreground = XWhitePixel(xDisplay, xScreen);
2916         gc_values.background = XBlackPixel(xDisplay, xScreen);
2917         lightSquareGC = wbPieceGC
2918           = XtGetGC(shellWidget, value_mask, &gc_values);
2919
2920         gc_values.foreground = XBlackPixel(xDisplay, xScreen);
2921         gc_values.background = XWhitePixel(xDisplay, xScreen);
2922         darkSquareGC = bwPieceGC
2923           = XtGetGC(shellWidget, value_mask, &gc_values);
2924
2925         if (DefaultDepth(xDisplay, xScreen) == 1) {
2926             /* Avoid XCopyPlane on 1-bit screens to work around Sun bug */
2927             gc_values.function = GXcopyInverted;
2928             copyInvertedGC = XtGetGC(shellWidget, value_mask, &gc_values);
2929             gc_values.function = GXcopy;
2930             if (XBlackPixel(xDisplay, xScreen) == 1) {
2931                 bwPieceGC = darkSquareGC;
2932                 wbPieceGC = copyInvertedGC;
2933             } else {
2934                 bwPieceGC = copyInvertedGC;
2935                 wbPieceGC = lightSquareGC;
2936             }
2937         }
2938     } else {
2939         gc_values.foreground = highlightSquareColor;
2940         gc_values.background = highlightSquareColor;
2941         highlineGC = XtGetGC(shellWidget, value_mask, &gc_values);
2942
2943         gc_values.foreground = premoveHighlightColor;
2944         gc_values.background = premoveHighlightColor;
2945         prelineGC = XtGetGC(shellWidget, value_mask, &gc_values);
2946
2947         gc_values.foreground = lightSquareColor;
2948         gc_values.background = darkSquareColor;
2949         lightSquareGC = XtGetGC(shellWidget, value_mask, &gc_values);
2950
2951         gc_values.foreground = darkSquareColor;
2952         gc_values.background = lightSquareColor;
2953         darkSquareGC = XtGetGC(shellWidget, value_mask, &gc_values);
2954
2955         gc_values.foreground = jailSquareColor;
2956         gc_values.background = jailSquareColor;
2957         jailSquareGC = XtGetGC(shellWidget, value_mask, &gc_values);
2958
2959         gc_values.foreground = whitePieceColor;
2960         gc_values.background = darkSquareColor;
2961         wdPieceGC = XtGetGC(shellWidget, value_mask, &gc_values);
2962
2963         gc_values.foreground = whitePieceColor;
2964         gc_values.background = lightSquareColor;
2965         wlPieceGC = XtGetGC(shellWidget, value_mask, &gc_values);
2966
2967         gc_values.foreground = whitePieceColor;
2968         gc_values.background = jailSquareColor;
2969         wjPieceGC = XtGetGC(shellWidget, value_mask, &gc_values);
2970
2971         gc_values.foreground = blackPieceColor;
2972         gc_values.background = darkSquareColor;
2973         bdPieceGC = XtGetGC(shellWidget, value_mask, &gc_values);
2974
2975         gc_values.foreground = blackPieceColor;
2976         gc_values.background = lightSquareColor;
2977         blPieceGC = XtGetGC(shellWidget, value_mask, &gc_values);
2978
2979         gc_values.foreground = blackPieceColor;
2980         gc_values.background = jailSquareColor;
2981         bjPieceGC = XtGetGC(shellWidget, value_mask, &gc_values);
2982     }
2983 }
2984
2985 void
2986 loadXIM (XImage *xim, XImage *xmask, char *filename, Pixmap *dest, Pixmap *mask)
2987 {
2988     int x, y, w, h, p;
2989     FILE *fp;
2990     Pixmap temp;
2991     XGCValues   values;
2992     GC maskGC;
2993
2994     fp = fopen(filename, "rb");
2995     if (!fp) {
2996         fprintf(stderr, _("%s: error loading XIM!\n"), programName);
2997         exit(1);
2998     }
2999
3000     w = fgetc(fp);
3001     h = fgetc(fp);
3002
3003     for (y=0; y<h; ++y) {
3004         for (x=0; x<h; ++x) {
3005             p = fgetc(fp);
3006
3007             switch (p) {
3008               case 0:
3009                 XPutPixel(xim, x, y, blackPieceColor);
3010                 if (xmask)
3011                   XPutPixel(xmask, x, y, WhitePixel(xDisplay,xScreen));
3012                 break;
3013               case 1:
3014                 XPutPixel(xim, x, y, darkSquareColor);
3015                 if (xmask)
3016                   XPutPixel(xmask, x, y, BlackPixel(xDisplay,xScreen));
3017                 break;
3018               case 2:
3019                 XPutPixel(xim, x, y, whitePieceColor);
3020                 if (xmask)
3021                   XPutPixel(xmask, x, y, WhitePixel(xDisplay,xScreen));
3022                 break;
3023               case 3:
3024                 XPutPixel(xim, x, y, lightSquareColor);
3025                 if (xmask)
3026                   XPutPixel(xmask, x, y, BlackPixel(xDisplay,xScreen));
3027                 break;
3028             }
3029         }
3030     }
3031
3032     fclose(fp);
3033
3034     /* create Pixmap of piece */
3035     *dest = XCreatePixmap(xDisplay, DefaultRootWindow(xDisplay),
3036                           w, h, xim->depth);
3037     XPutImage(xDisplay, *dest, lightSquareGC, xim,
3038               0, 0, 0, 0, w, h);
3039
3040     /* create Pixmap of clipmask
3041        Note: We assume the white/black pieces have the same
3042              outline, so we make only 6 masks. This is okay
3043              since the XPM clipmask routines do the same. */
3044     if (xmask) {
3045       temp = XCreatePixmap(xDisplay, DefaultRootWindow(xDisplay),
3046                             w, h, xim->depth);
3047       XPutImage(xDisplay, temp, lightSquareGC, xmask,
3048               0, 0, 0, 0, w, h);
3049
3050       /* now create the 1-bit version */
3051       *mask = XCreatePixmap(xDisplay, DefaultRootWindow(xDisplay),
3052                           w, h, 1);
3053
3054       values.foreground = 1;
3055       values.background = 0;
3056
3057       /* Don't use XtGetGC, not read only */
3058       maskGC = XCreateGC(xDisplay, *mask,
3059                     GCForeground | GCBackground, &values);
3060       XCopyPlane(xDisplay, temp, *mask, maskGC,
3061                   0, 0, squareSize, squareSize, 0, 0, 1);
3062       XFreePixmap(xDisplay, temp);
3063     }
3064 }
3065
3066
3067 char pieceBitmapNames[] = "pnbrqfeacwmohijgdvlsukpnsl";
3068
3069 void
3070 CreateXIMPieces ()
3071 {
3072     int piece, kind;
3073     char buf[MSG_SIZ];
3074     u_int ss;
3075     static char *ximkind[] = { "ll", "ld", "dl", "dd" };
3076     XImage *ximtemp;
3077
3078     ss = squareSize;
3079
3080     /* The XSynchronize calls were copied from CreatePieces.
3081        Not sure if needed, but can't hurt */
3082     XSynchronize(xDisplay, True); /* Work-around for xlib/xt
3083                                      buffering bug */
3084
3085     /* temp needed by loadXIM() */
3086     ximtemp = XGetImage(xDisplay, DefaultRootWindow(xDisplay),
3087                  0, 0, ss, ss, AllPlanes, XYPixmap);
3088
3089     if (strlen(appData.pixmapDirectory) == 0) {
3090       useImages = 0;
3091     } else {
3092         useImages = 1;
3093         if (appData.monoMode) {
3094           DisplayFatalError(_("XIM pieces cannot be used in monochrome mode"),
3095                             0, 2);
3096           ExitEvent(2);
3097         }
3098         fprintf(stderr, _("\nLoading XIMs...\n"));
3099         /* Load pieces */
3100         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
3101             fprintf(stderr, "%d", piece+1);
3102             for (kind=0; kind<4; kind++) {
3103                 fprintf(stderr, ".");
3104                 snprintf(buf, sizeof(buf), "%s/%s%c%s%u.xim",
3105                         ExpandPathName(appData.pixmapDirectory),
3106                         piece <= (int) WhiteKing ? "" : "w",
3107                         pieceBitmapNames[piece],
3108                         ximkind[kind], ss);
3109                 ximPieceBitmap[kind][piece] =
3110                   XGetImage(xDisplay, DefaultRootWindow(xDisplay),
3111                             0, 0, ss, ss, AllPlanes, XYPixmap);
3112                 if (appData.debugMode)
3113                   fprintf(stderr, _("(File:%s:) "), buf);
3114                 loadXIM(ximPieceBitmap[kind][piece],
3115                         ximtemp, buf,
3116                         &(xpmPieceBitmap2[kind][piece]),
3117                         &(ximMaskPm2[piece]));
3118                 if(piece <= (int)WhiteKing)
3119                     xpmPieceBitmap[kind][piece] = xpmPieceBitmap2[kind][piece];
3120             }
3121             fprintf(stderr," ");
3122         }
3123         /* Load light and dark squares */
3124         /* If the LSQ and DSQ pieces don't exist, we will
3125            draw them with solid squares. */
3126         snprintf(buf,sizeof(buf), "%s/lsq%u.xim", ExpandPathName(appData.pixmapDirectory), ss);
3127         if (access(buf, 0) != 0) {
3128             useImageSqs = 0;
3129         } else {
3130             useImageSqs = 1;
3131             fprintf(stderr, _("light square "));
3132             ximLightSquare=
3133               XGetImage(xDisplay, DefaultRootWindow(xDisplay),
3134                         0, 0, ss, ss, AllPlanes, XYPixmap);
3135             if (appData.debugMode)
3136               fprintf(stderr, _("(File:%s:) "), buf);
3137
3138             loadXIM(ximLightSquare, NULL, buf, &xpmLightSquare, NULL);
3139             fprintf(stderr, _("dark square "));
3140             snprintf(buf,sizeof(buf), "%s/dsq%u.xim",
3141                     ExpandPathName(appData.pixmapDirectory), ss);
3142             if (appData.debugMode)
3143               fprintf(stderr, _("(File:%s:) "), buf);
3144             ximDarkSquare=
3145               XGetImage(xDisplay, DefaultRootWindow(xDisplay),
3146                         0, 0, ss, ss, AllPlanes, XYPixmap);
3147             loadXIM(ximDarkSquare, NULL, buf, &xpmDarkSquare, NULL);
3148             xpmJailSquare = xpmLightSquare;
3149         }
3150         fprintf(stderr, _("Done.\n"));
3151     }
3152     XSynchronize(xDisplay, False); /* Work-around for xlib/xt buffering bug */
3153 }
3154
3155 static VariantClass oldVariant = (VariantClass) -1; // [HGM] pieces: redo every time variant changes
3156
3157 #if HAVE_LIBXPM
3158 void
3159 CreateXPMBoard (char *s, int kind)
3160 {
3161     XpmAttributes attr;
3162     attr.valuemask = 0;
3163     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
3164     if (XpmReadFileToPixmap(xDisplay, xBoardWindow, s, &(xpmBoardBitmap[kind]), NULL, &attr) == 0) {
3165         useTexture |= kind + 1; textureW[kind] = attr.width; textureH[kind] = attr.height;
3166     }
3167 }
3168
3169 void
3170 FreeXPMPieces ()
3171 {   // [HGM] to prevent resoucre leak on calling CreaeXPMPieces() a second time,
3172     // thisroutine has to be called t free the old piece pixmaps
3173     int piece, kind;
3174     for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++)
3175         for (kind=0; kind<4; kind++) XFreePixmap(xDisplay, xpmPieceBitmap2[kind][piece]);
3176     if(useImageSqs) {
3177         XFreePixmap(xDisplay, xpmLightSquare);
3178         XFreePixmap(xDisplay, xpmDarkSquare);
3179     }
3180 }
3181
3182 void
3183 CreateXPMPieces ()
3184 {
3185     int piece, kind, r;
3186     char buf[MSG_SIZ];
3187     u_int ss = squareSize;
3188     XpmAttributes attr;
3189     static char *xpmkind[] = { "ll", "ld", "dl", "dd" };
3190     XpmColorSymbol symbols[4];
3191     static int redo = False;
3192
3193     if(redo) FreeXPMPieces(); else redo = 1;
3194
3195     /* The XSynchronize calls were copied from CreatePieces.
3196        Not sure if needed, but can't hurt */
3197     XSynchronize(xDisplay, True); /* Work-around for xlib/xt buffering bug */
3198
3199     /* Setup translations so piece colors match square colors */
3200     symbols[0].name = "light_piece";
3201     symbols[0].value = appData.whitePieceColor;
3202     symbols[1].name = "dark_piece";
3203     symbols[1].value = appData.blackPieceColor;
3204     symbols[2].name = "light_square";
3205     symbols[2].value = appData.lightSquareColor;
3206     symbols[3].name = "dark_square";
3207     symbols[3].value = appData.darkSquareColor;
3208
3209     attr.valuemask = XpmColorSymbols;
3210     attr.colorsymbols = symbols;
3211     attr.numsymbols = 4;
3212
3213     if (appData.monoMode) {
3214       DisplayFatalError(_("XPM pieces cannot be used in monochrome mode"),
3215                         0, 2);
3216       ExitEvent(2);
3217     }
3218     if (strlen(appData.pixmapDirectory) == 0) {
3219         XpmPieces* pieces = builtInXpms;
3220         useImages = 1;
3221         /* Load pieces */
3222         while (pieces->size != squareSize && pieces->size) pieces++;
3223         if (!pieces->size) {
3224           fprintf(stderr, _("No builtin XPM pieces of size %d\n"), squareSize);
3225           exit(1);
3226         }
3227         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
3228             for (kind=0; kind<4; kind++) {
3229
3230                 if ((r=XpmCreatePixmapFromData(xDisplay, xBoardWindow,
3231                                                pieces->xpm[piece][kind],
3232                                                &(xpmPieceBitmap2[kind][piece]),
3233                                                NULL, &attr)) != 0) {
3234                   fprintf(stderr, _("Error %d loading XPM image \"%s\"\n"),
3235                           r, buf);
3236                   exit(1);
3237                 }
3238                 if(piece <= (int) WhiteKing)
3239                     xpmPieceBitmap[kind][piece] = xpmPieceBitmap2[kind][piece];
3240             }
3241         }
3242         useImageSqs = 0;
3243         xpmJailSquare = xpmLightSquare;
3244     } else {
3245         useImages = 1;
3246
3247         fprintf(stderr, _("\nLoading XPMs...\n"));
3248
3249         /* Load pieces */
3250         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
3251             fprintf(stderr, "%d ", piece+1);
3252             for (kind=0; kind<4; kind++) {
3253               snprintf(buf, sizeof(buf), "%s/%s%c%s%u.xpm",
3254                         ExpandPathName(appData.pixmapDirectory),
3255                         piece > (int) WhiteKing ? "w" : "",
3256                         pieceBitmapNames[piece],
3257                         xpmkind[kind], ss);
3258                 if (appData.debugMode) {
3259                     fprintf(stderr, _("(File:%s:) "), buf);
3260                 }
3261                 if ((r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
3262                                            &(xpmPieceBitmap2[kind][piece]),
3263                                            NULL, &attr)) != 0) {
3264                     if(piece != (int)WhiteKing && piece > (int)WhiteQueen) {
3265                       // [HGM] missing: read of unorthodox piece failed; substitute King.
3266                       snprintf(buf, sizeof(buf), "%s/k%s%u.xpm",
3267                                 ExpandPathName(appData.pixmapDirectory),
3268                                 xpmkind[kind], ss);
3269                         if (appData.debugMode) {
3270                             fprintf(stderr, _("(Replace by File:%s:) "), buf);
3271                         }
3272                         r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
3273                                                 &(xpmPieceBitmap2[kind][piece]),
3274                                                 NULL, &attr);
3275                     }
3276                     if (r != 0) {
3277                         fprintf(stderr, _("Error %d loading XPM file \"%s\"\n"),
3278                                 r, buf);
3279                         exit(1);
3280                     }
3281                 }
3282                 if(piece <= (int) WhiteKing)
3283                     xpmPieceBitmap[kind][piece] = xpmPieceBitmap2[kind][piece];
3284             }
3285         }
3286         /* Load light and dark squares */
3287         /* If the LSQ and DSQ pieces don't exist, we will
3288            draw them with solid squares. */
3289         fprintf(stderr, _("light square "));
3290         snprintf(buf, sizeof(buf), "%s/lsq%u.xpm", ExpandPathName(appData.pixmapDirectory), ss);
3291         if (access(buf, 0) != 0) {
3292             useImageSqs = 0;
3293         } else {
3294             useImageSqs = 1;
3295             if (appData.debugMode)
3296               fprintf(stderr, _("(File:%s:) "), buf);
3297
3298             if ((r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
3299                                        &xpmLightSquare, NULL, &attr)) != 0) {
3300                 fprintf(stderr, _("Error %d loading XPM file \"%s\"\n"), r, buf);
3301                 exit(1);
3302             }
3303             fprintf(stderr, _("dark square "));
3304             snprintf(buf, sizeof(buf), "%s/dsq%u.xpm",
3305                     ExpandPathName(appData.pixmapDirectory), ss);
3306             if (appData.debugMode) {
3307                 fprintf(stderr, _("(File:%s:) "), buf);
3308             }
3309             if ((r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
3310                                        &xpmDarkSquare, NULL, &attr)) != 0) {
3311                 fprintf(stderr, _("Error %d loading XPM file \"%s\"\n"), r, buf);
3312                 exit(1);
3313             }
3314         }
3315         xpmJailSquare = xpmLightSquare;
3316         fprintf(stderr, _("Done.\n"));
3317     }
3318     oldVariant = -1; // kludge to force re-makig of animation masks
3319     XSynchronize(xDisplay, False); /* Work-around for xlib/xt
3320                                       buffering bug */
3321 }
3322 #endif /* HAVE_LIBXPM */
3323
3324 #if HAVE_LIBXPM
3325 /* No built-in bitmaps */
3326 void CreatePieces()
3327 {
3328     int piece, kind;
3329     char buf[MSG_SIZ];
3330     u_int ss = squareSize;
3331
3332     XSynchronize(xDisplay, True); /* Work-around for xlib/xt
3333                                      buffering bug */
3334
3335     for (kind = SOLID; kind <= (appData.monoMode ? OUTLINE : SOLID); kind++) {
3336         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
3337           snprintf(buf, MSG_SIZ, "%s%c%u%c.bm", piece > (int)WhiteKing ? "w" : "",
3338                    pieceBitmapNames[piece],
3339                    ss, kind == SOLID ? 's' : 'o');
3340           ReadBitmap(&pieceBitmap2[kind][piece], buf, NULL, ss, ss);
3341           if(piece <= (int)WhiteKing)
3342             pieceBitmap[kind][piece] = pieceBitmap2[kind][piece];
3343         }
3344     }
3345
3346     XSynchronize(xDisplay, False); /* Work-around for xlib/xt
3347                                       buffering bug */
3348 }
3349 #else
3350 /* With built-in bitmaps */
3351 void
3352 CreatePieces ()
3353 {
3354     BuiltInBits* bib = builtInBits;
3355     int piece, kind;
3356     char buf[MSG_SIZ];
3357     u_int ss = squareSize;
3358
3359     XSynchronize(xDisplay, True); /* Work-around for xlib/xt
3360                                      buffering bug */
3361
3362     while (bib->squareSize != ss && bib->squareSize != 0) bib++;
3363
3364     for (kind = SOLID; kind <= (appData.monoMode ? OUTLINE : SOLID); kind++) {
3365         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
3366           snprintf(buf, MSG_SIZ, "%s%c%u%c.bm", piece > (int)WhiteKing ? "w" : "",
3367                    pieceBitmapNames[piece],
3368                    ss, kind == SOLID ? 's' : 'o');
3369           ReadBitmap(&pieceBitmap2[kind][piece], buf,
3370                      bib->bits[kind][piece], ss, ss);
3371           if(piece <= (int)WhiteKing)
3372             pieceBitmap[kind][piece] = pieceBitmap2[kind][piece];
3373         }
3374     }
3375
3376     XSynchronize(xDisplay, False); /* Work-around for xlib/xt
3377                                       buffering bug */
3378 }
3379 #endif
3380
3381 void
3382 ReadBitmap (Pixmap *pm, String name, unsigned char bits[], u_int wreq, u_int hreq)
3383 {
3384     int x_hot, y_hot;
3385     u_int w, h;
3386     int errcode;
3387     char msg[MSG_SIZ], fullname[MSG_SIZ];
3388
3389     if (*appData.bitmapDirectory != NULLCHAR) {
3390       safeStrCpy(fullname, appData.bitmapDirectory, sizeof(fullname)/sizeof(fullname[0]) );
3391       strncat(fullname, "/", MSG_SIZ - strlen(fullname) - 1);
3392       strncat(fullname, name, MSG_SIZ - strlen(fullname) - 1);
3393       errcode = XReadBitmapFile(xDisplay, xBoardWindow, fullname,
3394                                 &w, &h, pm, &x_hot, &y_hot);
3395       fprintf(stderr, "load %s\n", name);
3396         if (errcode != BitmapSuccess) {
3397             switch (errcode) {
3398               case BitmapOpenFailed:
3399                 snprintf(msg, sizeof(msg), _("Can't open bitmap file %s"), fullname);
3400                 break;
3401               case BitmapFileInvalid:
3402                 snprintf(msg, sizeof(msg), _("Invalid bitmap in file %s"), fullname);
3403                 break;
3404               case BitmapNoMemory:
3405                 snprintf(msg, sizeof(msg), _("Ran out of memory reading bitmap file %s"),
3406                         fullname);
3407                 break;
3408               default:
3409                 snprintf(msg, sizeof(msg), _("Unknown XReadBitmapFile error %d on file %s"),
3410                         errcode, fullname);
3411                 break;
3412             }
3413             fprintf(stderr, _("%s: %s...using built-in\n"),
3414                     programName, msg);
3415         } else if (w != wreq || h != hreq) {
3416             fprintf(stderr,
3417                     _("%s: Bitmap %s is %dx%d, not %dx%d...using built-in\n"),
3418                     programName, fullname, w, h, wreq, hreq);
3419         } else {
3420             return;
3421         }
3422     }
3423     if (bits != NULL) {
3424         *pm = XCreateBitmapFromData(xDisplay, xBoardWindow, (char *) bits,
3425                                     wreq, hreq);
3426     }
3427 }
3428
3429 void
3430 CreateGrid ()
3431 {
3432     int i, j;
3433
3434     if (lineGap == 0) return;
3435
3436     /* [HR] Split this into 2 loops for non-square boards. */
3437
3438     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
3439         gridSegments[i].x1 = 0;
3440         gridSegments[i].x2 =
3441           lineGap + BOARD_WIDTH * (squareSize + lineGap);
3442         gridSegments[i].y1 = gridSegments[i].y2
3443           = lineGap / 2 + (i * (squareSize + lineGap));
3444     }
3445
3446     for (j = 0; j < BOARD_WIDTH + 1; j++) {
3447         gridSegments[j + i].y1 = 0;
3448         gridSegments[j + i].y2 =
3449           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
3450         gridSegments[j + i].x1 = gridSegments[j + i].x2
3451           = lineGap / 2 + (j * (squareSize + lineGap));
3452     }
3453 }
3454
3455 int nrOfMenuItems = 7;
3456 MenuListItem menuItemList[150] = {
3457     { "LoadNextGameProc", LoadNextGameProc },
3458     { "LoadPrevGameProc", LoadPrevGameProc },
3459     { "ReloadGameProc", ReloadGameProc },
3460     { "ReloadPositionProc", ReloadPositionProc },
3461 #ifndef OPTIONSDIALOG
3462     { "AlwaysQueenProc", AlwaysQueenProc },
3463     { "AnimateDraggingProc", AnimateDraggingProc },
3464     { "AnimateMovingProc", AnimateMovingProc },
3465     { "AutoflagProc", AutoflagProc },
3466     { "AutoflipProc", AutoflipProc },
3467     { "BlindfoldProc", BlindfoldProc },
3468     { "FlashMovesProc", FlashMovesProc },
3469 #if HIGHDRAG
3470     { "HighlightDraggingProc", HighlightDraggingProc },
3471 #endif
3472     { "HighlightLastMoveProc", HighlightLastMoveProc },
3473 //    { "IcsAlarmProc", IcsAlarmProc },
3474     { "MoveSoundProc", MoveSoundProc },
3475     { "PeriodicUpdatesProc", PeriodicUpdatesProc },
3476     { "PopupExitMessageProc", PopupExitMessageProc },
3477     { "PopupMoveErrorsProc", PopupMoveErrorsProc },
3478 //    { "PremoveProc", PremoveProc },
3479     { "ShowCoordsProc", ShowCoordsProc },
3480     { "ShowThinkingProc", ShowThinkingProc },
3481     { "HideThinkingProc", HideThinkingProc },
3482     { "TestLegalityProc", TestLegalityProc },
3483 #endif
3484     { "AboutGameProc", AboutGameEvent },
3485     { "DebugProc", DebugProc },
3486     { "NothingProc", NothingProc },
3487   {NULL, NothingProc}
3488 };
3489
3490 int
3491 Equal(char *p, char *s)
3492 {   // compare strings skipping spaces in second
3493     while(*s) {
3494         if(*s == ' ') { s++; continue; }
3495         if(*s++ != *p++) return 0;
3496     }
3497     return !*p;
3498 }
3499
3500 void
3501 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3502 {   // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
3503     int i;
3504     if(*nprms == 0) return;
3505     for(i=0; menuItemList[i].name; i++) {
3506         if(Equal(prms[0], menuItemList[i].name)) {
3507             (menuItemList[i].proc) ();
3508             return;
3509         }
3510     }
3511 }
3512
3513 static void
3514 MenuBarSelect (Widget w, caddr_t addr, caddr_t index)
3515 {
3516     MenuProc *proc = (MenuProc *) addr;
3517
3518     (proc)();
3519 }
3520
3521 static void
3522 MenuEngineSelect (Widget w, caddr_t addr, caddr_t index)
3523 {
3524     RecentEngineEvent((int) (intptr_t) addr);
3525 }
3526
3527 // some stuff that must remain in front-end
3528 static Widget mainBar, currentMenu;
3529 static int wtot, nr = 0, widths[10];
3530
3531 void
3532 AppendMenuItem (char *text, char *name, MenuProc *action)
3533 {
3534     int j;
3535     Widget entry;
3536     Arg args[16];
3537
3538     j = 0;
3539     XtSetArg(args[j], XtNleftMargin, 20);   j++;
3540     XtSetArg(args[j], XtNrightMargin, 20);  j++;
3541
3542         if (strcmp(text, "----") == 0) {
3543           entry = XtCreateManagedWidget(text, smeLineObjectClass,
3544                                           currentMenu, args, j);
3545         } else {
3546           XtSetArg(args[j], XtNlabel, XtNewString(_(text)));
3547             entry = XtCreateManagedWidget(name, smeBSBObjectClass,
3548                                           currentMenu, args, j+1);
3549             XtAddCallback(entry, XtNcallback,
3550                           (XtCallbackProc) (strcmp(name, "recent") ? MenuBarSelect : MenuEngineSelect),
3551                           (caddr_t) action);
3552         }
3553 }
3554
3555 void
3556 CreateMenuButton (char *name, Menu *mb)
3557 {   // create menu button on main bar, and shell for pull-down list
3558     int i, j;
3559     Arg args[16];
3560     Dimension w;
3561
3562         j = 0;
3563         XtSetArg(args[j], XtNmenuName, XtNewString(name));  j++;
3564         XtSetArg(args[j], XtNlabel, XtNewString(_(mb->name)));  j++;
3565         XtSetArg(args[j], XtNborderWidth, 0);                   j++;
3566         mb->subMenu = XtCreateManagedWidget(mb->name, menuButtonWidgetClass,
3567                                        mainBar, args, j);
3568     currentMenu = XtCreatePopupShell(name, simpleMenuWidgetClass,
3569                               mainBar, NULL, 0);
3570         j = 0;
3571         XtSetArg(args[j], XtNwidth, &w);                   j++;
3572         XtGetValues(mb->subMenu, args, j);
3573         wtot += mb->textWidth = widths[nr++] = w;
3574 }
3575
3576 Widget
3577 CreateMenuBar (Menu *mb, int boardWidth)
3578 {
3579     int i, j;
3580     Arg args[16];
3581     char menuName[MSG_SIZ];
3582     Dimension w;
3583     Menu *ma = mb;
3584
3585     // create bar itself
3586     j = 0;
3587     XtSetArg(args[j], XtNorientation, XtorientHorizontal);  j++;
3588     XtSetArg(args[j], XtNvSpace, 0);                        j++;
3589     XtSetArg(args[j], XtNborderWidth, 0);                   j++;
3590     mainBar = XtCreateWidget("menuBar", boxWidgetClass,
3591                              formWidget, args, j);
3592
3593     CreateMainMenus(mb); // put menus in bar according to description in back-end
3594
3595     // size buttons to make menu bar fit, clipping menu names where necessary
3596     while(wtot > boardWidth - 40) {
3597         int wmax=0, imax=0;
3598         for(i=0; i<nr; i++) if(widths[i] > wmax) wmax = widths[imax=i];
3599         widths[imax]--;
3600         wtot--;
3601     }
3602     for(i=0; i<nr; i++) if(widths[i] != ma[i].textWidth) {
3603         j = 0;
3604         XtSetArg(args[j], XtNwidth, widths[i]);                   j++;
3605         XtSetValues(ma[i].subMenu, args, j);
3606     }
3607
3608     return mainBar;
3609 }
3610
3611 Widget
3612 CreateButtonBar (MenuItem *mi)
3613 {
3614     int j;
3615     Widget button, buttonBar;
3616     Arg args[16];
3617
3618     j = 0;
3619     XtSetArg(args[j], XtNorientation, XtorientHorizontal); j++;
3620     if (tinyLayout) {
3621         XtSetArg(args[j], XtNhSpace, 0); j++;
3622     }
3623     XtSetArg(args[j], XtNborderWidth, 0); j++;
3624     XtSetArg(args[j], XtNvSpace, 0);                        j++;
3625     buttonBar = XtCreateWidget("buttonBar", boxWidgetClass,
3626                                formWidget, args, j);
3627
3628     while (mi->string != NULL) {
3629         j = 0;
3630         if (tinyLayout) {
3631             XtSetArg(args[j], XtNinternalWidth, 2); j++;
3632             XtSetArg(args[j], XtNborderWidth, 0); j++;
3633         }
3634       XtSetArg(args[j], XtNlabel, XtNewString(_(mi->string))); j++;
3635         button = XtCreateManagedWidget(mi->string, commandWidgetClass,
3636                                        buttonBar, args, j);
3637         XtAddCallback(button, XtNcallback,
3638                       (XtCallbackProc) MenuBarSelect,
3639                       (caddr_t) mi->proc);
3640         mi++;
3641     }
3642     return buttonBar;
3643 }
3644
3645 Widget
3646 CreatePieceMenu (char *name, int color)
3647 {
3648     int i;
3649     Widget entry, menu;
3650     Arg args[16];
3651     ChessSquare selection;
3652
3653     menu = XtCreatePopupShell(name, simpleMenuWidgetClass,
3654                               boardWidget, args, 0);
3655
3656     for (i = 0; i < PIECE_MENU_SIZE; i++) {
3657         String item = pieceMenuStrings[color][i];
3658
3659         if (strcmp(item, "----") == 0) {
3660             entry = XtCreateManagedWidget(item, smeLineObjectClass,
3661                                           menu, NULL, 0);
3662         } else {
3663           XtSetArg(args[0], XtNlabel, XtNewString(_(item)));
3664             entry = XtCreateManagedWidget(item, smeBSBObjectClass,
3665                                 menu, args, 1);
3666             selection = pieceMenuTranslation[color][i];
3667             XtAddCallback(entry, XtNcallback,
3668                           (XtCallbackProc) PieceMenuSelect,
3669                           (caddr_t) selection);
3670             if (selection == WhitePawn || selection == BlackPawn) {
3671                 XtSetArg(args[0], XtNpopupOnEntry, entry);
3672                 XtSetValues(menu, args, 1);
3673             }
3674         }
3675     }
3676     return menu;
3677 }
3678
3679 void
3680 CreatePieceMenus ()
3681 {
3682     int i;
3683     Widget entry;
3684     Arg args[16];
3685     ChessSquare selection;
3686
3687     whitePieceMenu = CreatePieceMenu("menuW", 0);
3688     blackPieceMenu = CreatePieceMenu("menuB", 1);
3689
3690     if(appData.pieceMenu) // [HGM] sweep: no idea what this was good for, but it stopped reporting button events outside the window
3691     XtRegisterGrabAction(PieceMenuPopup, True,
3692                          (unsigned)(ButtonPressMask|ButtonReleaseMask),
3693                          GrabModeAsync, GrabModeAsync);
3694
3695     XtSetArg(args[0], XtNlabel, _("Drop"));
3696     dropMenu = XtCreatePopupShell("menuD", simpleMenuWidgetClass,
3697                                   boardWidget, args, 1);
3698     for (i = 0; i < DROP_MENU_SIZE; i++) {
3699         String item = dropMenuStrings[i];
3700
3701         if (strcmp(item, "----") == 0) {
3702             entry = XtCreateManagedWidget(item, smeLineObjectClass,
3703                                           dropMenu, NULL, 0);
3704         } else {
3705           XtSetArg(args[0], XtNlabel, XtNewString(_(item)));
3706             entry = XtCreateManagedWidget(item, smeBSBObjectClass,
3707                                 dropMenu, args, 1);
3708             selection = dropMenuTranslation[i];
3709             XtAddCallback(entry, XtNcallback,
3710                           (XtCallbackProc) DropMenuSelect,
3711                           (caddr_t) selection);
3712         }
3713     }
3714 }
3715
3716 void
3717 SetupDropMenu ()
3718 {
3719     int i, j, count;
3720     char label[32];
3721     Arg args[16];
3722     Widget entry;
3723     char* p;
3724
3725     for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
3726         entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
3727         p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
3728                    dmEnables[i].piece);
3729         XtSetSensitive(entry, p != NULL || !appData.testLegality
3730                        /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
3731                                        && !appData.icsActive));
3732         count = 0;
3733         while (p && *p++ == dmEnables[i].piece) count++;
3734         snprintf(label, sizeof(label), "%s  %d", dmEnables[i].widget, count);
3735         j = 0;
3736         XtSetArg(args[j], XtNlabel, label); j++;
3737         XtSetValues(entry, args, j);
3738     }
3739 }
3740
3741 void
3742 PieceMenuPopup (Widget w, XEvent *event, String *params, Cardinal *num_params)
3743 {
3744     String whichMenu; int menuNr = -2;
3745     shiftKey = strcmp(params[0], "menuW"); // used to indicate black
3746     if (event->type == ButtonRelease)
3747         menuNr = RightClick(Release, event->xbutton.x, event->xbutton.y, &pmFromX, &pmFromY);
3748     else if (event->type == ButtonPress)
3749         menuNr = RightClick(Press,   event->xbutton.x, event->xbutton.y, &pmFromX, &pmFromY);
3750     switch(menuNr) {
3751       case 0: whichMenu = params[0]; break;
3752       case 1: SetupDropMenu(); whichMenu = "menuD"; break;
3753       case 2:
3754       case -1: if (errorUp) ErrorPopDown();
3755       default: return;
3756     }
3757     XtPopupSpringLoaded(XtNameToWidget(boardWidget, whichMenu));
3758 }
3759
3760 static void
3761 PieceMenuSelect (Widget w, ChessSquare piece, caddr_t junk)
3762 {
3763     if (pmFromX < 0 || pmFromY < 0) return;
3764     EditPositionMenuEvent(piece, pmFromX, pmFromY);
3765 }
3766
3767 static void
3768 DropMenuSelect (Widget w, ChessSquare piece, caddr_t junk)
3769 {
3770     if (pmFromX < 0 || pmFromY < 0) return;
3771     DropMenuEvent(piece, pmFromX, pmFromY);
3772 }
3773
3774 void
3775 WhiteClock (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3776 {
3777     shiftKey = prms[0][0] & 1;
3778     ClockClick(0);
3779 }
3780
3781 void
3782 BlackClock (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3783 {
3784     shiftKey = prms[0][0] & 1;
3785     ClockClick(1);
3786 }
3787
3788
3789 /*
3790  * If the user selects on a border boundary, return -1; if off the board,
3791  *   return -2.  Otherwise map the event coordinate to the square.
3792  */
3793 int
3794 EventToSquare (int x, int limit)
3795 {
3796     if (x <= 0)
3797       return -2;
3798     if (x < lineGap)
3799       return -1;
3800     x -= lineGap;
3801     if ((x % (squareSize + lineGap)) >= squareSize)
3802       return -1;
3803     x /= (squareSize + lineGap);
3804     if (x >= limit)
3805       return -2;
3806     return x;
3807 }
3808
3809 static void
3810 do_flash_delay (unsigned long msec)
3811 {
3812     TimeDelay(msec);
3813 }
3814
3815 static void
3816 drawHighlight (int file, int rank, GC gc)
3817 {
3818     int x, y;
3819
3820     if (lineGap == 0) return;
3821
3822     if (flipView) {
3823         x = lineGap/2 + ((BOARD_WIDTH-1)-file) *
3824           (squareSize + lineGap);
3825         y = lineGap/2 + rank * (squareSize + lineGap);
3826     } else {
3827         x = lineGap/2 + file * (squareSize + lineGap);
3828         y = lineGap/2 + ((BOARD_HEIGHT-1)-rank) *
3829           (squareSize + lineGap);
3830     }
3831
3832     XDrawRectangle(xDisplay, xBoardWindow, gc, x, y,
3833                    squareSize+lineGap, squareSize+lineGap);
3834 }
3835
3836 int hi1X = -1, hi1Y = -1, hi2X = -1, hi2Y = -1;
3837 int pm1X = -1, pm1Y = -1, pm2X = -1, pm2Y = -1;
3838
3839 void
3840 SetHighlights (int fromX, int fromY, int toX, int toY)
3841 {
3842     if (hi1X != fromX || hi1Y != fromY) {
3843         if (hi1X >= 0 && hi1Y >= 0) {
3844             drawHighlight(hi1X, hi1Y, lineGC);
3845         }
3846     } // [HGM] first erase both, then draw new!
3847
3848     if (hi2X != toX || hi2Y != toY) {
3849         if (hi2X >= 0 && hi2Y >= 0) {
3850             drawHighlight(hi2X, hi2Y, lineGC);
3851         }
3852     }
3853     if (hi1X != fromX || hi1Y != fromY) {
3854         if (fromX >= 0 && fromY >= 0) {
3855             drawHighlight(fromX, fromY, highlineGC);
3856         }
3857     }
3858     if (hi2X != toX || hi2Y != toY) {
3859         if (toX >= 0 && toY >= 0) {
3860             drawHighlight(toX, toY, highlineGC);
3861         }
3862     }
3863
3864     if(toX<0) // clearing the highlights must have damaged arrow
3865         DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y); // for now, redraw it (should really be cleared!)
3866
3867     hi1X = fromX;
3868     hi1Y = fromY;
3869     hi2X = toX;
3870     hi2Y = toY;
3871 }
3872
3873 void
3874 ClearHighlights ()
3875 {
3876     SetHighlights(-1, -1, -1, -1);
3877 }
3878
3879
3880 void
3881 SetPremoveHighlights (int fromX, int fromY, int toX, int toY)
3882 {
3883     if (pm1X != fromX || pm1Y != fromY) {
3884         if (pm1X >= 0 && pm1Y >= 0) {
3885             drawHighlight(pm1X, pm1Y, lineGC);
3886         }
3887         if (fromX >= 0 && fromY >= 0) {
3888             drawHighlight(fromX, fromY, prelineGC);
3889         }
3890     }
3891     if (pm2X != toX || pm2Y != toY) {
3892         if (pm2X >= 0 && pm2Y >= 0) {
3893             drawHighlight(pm2X, pm2Y, lineGC);
3894         }
3895         if (toX >= 0 && toY >= 0) {
3896             drawHighlight(toX, toY, prelineGC);
3897         }
3898     }
3899     pm1X = fromX;
3900     pm1Y = fromY;
3901     pm2X = toX;
3902     pm2Y = toY;
3903 }
3904
3905 void
3906 ClearPremoveHighlights ()
3907 {
3908   SetPremoveHighlights(-1, -1, -1, -1);
3909 }
3910
3911 static int
3912 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
3913 {
3914     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
3915     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
3916     *x0 = 0; *y0 = 0;
3917     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
3918     if(textureW[kind] < W*squareSize)
3919         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
3920     else
3921         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
3922     if(textureH[kind] < H*squareSize)
3923         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
3924     else
3925         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
3926     return 1;
3927 }
3928
3929 static void
3930 BlankSquare (int x, int y, int color, ChessSquare piece, Drawable dest, int fac)
3931 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
3932     int x0, y0;
3933     if (useImages && color != 2 && (useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
3934         XCopyArea(xDisplay, xpmBoardBitmap[color], dest, wlPieceGC, x0, y0,
3935                   squareSize, squareSize, x*fac, y*fac);
3936     } else
3937     if (useImages && useImageSqs) {
3938         Pixmap pm;
3939         switch (color) {
3940           case 1: /* light */
3941             pm = xpmLightSquare;
3942             break;
3943           case 0: /* dark */
3944             pm = xpmDarkSquare;
3945             break;
3946           case 2: /* neutral */
3947           default:
3948             pm = xpmJailSquare;
3949             break;
3950         }
3951         XCopyArea(xDisplay, pm, dest, wlPieceGC, 0, 0,
3952                   squareSize, squareSize, x*fac, y*fac);
3953     } else {
3954         GC gc;
3955         switch (color) {
3956           case 1: /* light */
3957             gc = lightSquareGC;
3958             break;
3959           case 0: /* dark */
3960             gc = darkSquareGC;
3961             break;
3962           case 2: /* neutral */
3963           default:
3964             gc = jailSquareGC;
3965             break;
3966         }
3967         XFillRectangle(xDisplay, dest, gc, x*fac, y*fac, squareSize, squareSize);
3968     }
3969 }
3970
3971 /*
3972    I split out the routines to draw a piece so that I could
3973    make a generic flash routine.
3974 */
3975 static void
3976 monoDrawPiece_1bit (ChessSquare piece, int square_color, int x, int y, Drawable dest)
3977 {
3978     /* Avoid XCopyPlane on 1-bit screens to work around Sun bug */
3979     switch (square_color) {
3980       case 1: /* light */
3981       case 2: /* neutral */
3982       default:
3983         XCopyArea(xDisplay, (int) piece < (int) BlackPawn
3984                   ? *pieceToOutline(piece)
3985                   : *pieceToSolid(piece),
3986                   dest, bwPieceGC, 0, 0,
3987                   squareSize, squareSize, x, y);
3988         break;
3989       case 0: /* dark */
3990         XCopyArea(xDisplay, (int) piece < (int) BlackPawn
3991                   ? *pieceToSolid(piece)
3992                   : *pieceToOutline(piece),
3993                   dest, wbPieceGC, 0, 0,
3994                   squareSize, squareSize, x, y);
3995         break;
3996     }
3997 }
3998
3999 static void
4000 monoDrawPiece (ChessSquare piece, int square_color, int x, int y, Drawable dest)
4001 {
4002     switch (square_color) {
4003       case 1: /* light */
4004       case 2: /* neutral */
4005       default:
4006         XCopyPlane(xDisplay, (int) piece < (int) BlackPawn
4007                    ? *pieceToOutline(piece)
4008                    : *pieceToSolid(piece),
4009                    dest, bwPieceGC, 0, 0,
4010                    squareSize, squareSize, x, y, 1);
4011         break;
4012       case 0: /* dark */
4013         XCopyPlane(xDisplay, (int) piece < (int) BlackPawn
4014                    ? *pieceToSolid(piece)
4015                    : *pieceToOutline(piece),
4016                    dest, wbPieceGC, 0, 0,
4017                    squareSize, squareSize, x, y, 1);
4018         break;
4019     }
4020 }
4021
4022 static void
4023 colorDrawPiece (ChessSquare piece, int square_color, int x, int y, Drawable dest)
4024 {
4025     if(pieceToSolid(piece) == NULL) return; // [HGM] bitmaps: make it non-fatal if we have no bitmap;
4026     switch (square_color) {
4027       case 1: /* light */
4028         XCopyPlane(xDisplay, *pieceToSolid(piece),
4029                    dest, (int) piece < (int) BlackPawn
4030                    ? wlPieceGC : blPieceGC, 0, 0,
4031                    squareSize, squareSize, x, y, 1);
4032         break;
4033       case 0: /* dark */
4034         XCopyPlane(xDisplay, *pieceToSolid(piece),
4035                    dest, (int) piece < (int) BlackPawn
4036                    ? wdPieceGC : bdPieceGC, 0, 0,
4037                    squareSize, squareSize, x, y, 1);
4038         break;
4039       case 2: /* neutral */
4040       default:
4041         XCopyPlane(xDisplay, *pieceToSolid(piece),
4042                    dest, (int) piece < (int) BlackPawn
4043                    ? wjPieceGC : bjPieceGC, 0, 0,
4044                    squareSize, squareSize, x, y, 1);
4045         break;
4046     }
4047 }
4048
4049 static void
4050 colorDrawPieceImage (ChessSquare piece, int square_color, int x, int y, Drawable dest)
4051 {
4052     int kind, p = piece;
4053
4054     switch (square_color) {
4055       case 1: /* light */
4056       case 2: /* neutral */
4057       default:
4058         if ((int)piece < (int) BlackPawn) {
4059             kind = 0;
4060         } else {
4061             kind = 2;
4062             piece -= BlackPawn;
4063         }
4064         break;
4065       case 0: /* dark */
4066         if ((int)piece < (int) BlackPawn) {
4067             kind = 1;
4068         } else {
4069             kind = 3;
4070             piece -= BlackPawn;
4071         }
4072         break;
4073     }
4074     if(appData.upsideDown && flipView) { kind ^= 2; p += p < BlackPawn ? BlackPawn : -BlackPawn; }// swap white and black pieces
4075     if(useTexture & square_color+1) {
4076         BlankSquare(x, y, square_color, piece, dest, 1); // erase previous contents with background
4077         XSetClipMask(xDisplay, wlPieceGC, xpmMask[p]);
4078         XSetClipOrigin(xDisplay, wlPieceGC, x, y);
4079         XCopyArea(xDisplay, xpmPieceBitmap[kind][piece], dest, wlPieceGC, 0, 0, squareSize, squareSize, x, y);
4080         XSetClipMask(xDisplay, wlPieceGC, None);
4081         XSetClipOrigin(xDisplay, wlPieceGC, 0, 0);
4082     } else
4083     XCopyArea(xDisplay, xpmPieceBitmap[kind][piece],
4084               dest, wlPieceGC, 0, 0,
4085               squareSize, squareSize, x, y);
4086 }
4087
4088 typedef void (*DrawFunc)();
4089
4090 DrawFunc
4091 ChooseDrawFunc ()
4092 {
4093     if (appData.monoMode) {
4094         if (DefaultDepth(xDisplay, xScreen) == 1) {
4095             return monoDrawPiece_1bit;
4096         } else {
4097             return monoDrawPiece;
4098         }
4099     } else {
4100         if (useImages)
4101           return colorDrawPieceImage;
4102         else
4103           return colorDrawPiece;
4104     }
4105 }
4106
4107 /* [HR] determine square color depending on chess variant. */
4108 static int
4109 SquareColor (int row, int column)
4110 {
4111     int square_color;
4112
4113     if (gameInfo.variant == VariantXiangqi) {
4114         if (column >= 3 && column <= 5 && row >= 0 && row <= 2) {
4115             square_color = 1;
4116         } else if (column >= 3 && column <= 5 && row >= 7 && row <= 9) {
4117             square_color = 0;
4118         } else if (row <= 4) {
4119             square_color = 0;
4120         } else {
4121             square_color = 1;
4122         }
4123     } else {
4124         square_color = ((column + row) % 2) == 1;
4125     }
4126
4127     /* [hgm] holdings: next line makes all holdings squares light */
4128     if(column < BOARD_LEFT || column >= BOARD_RGHT) square_color = 1;
4129
4130     return square_color;
4131 }
4132
4133 void
4134 DrawSquare (int row, int column, ChessSquare piece, int do_flash)
4135 {
4136     int square_color, x, y, direction, font_ascent, font_descent;
4137     int i;
4138     char string[2];
4139     XCharStruct overall;
4140     DrawFunc drawfunc;
4141     int flash_delay;
4142
4143     /* Calculate delay in milliseconds (2-delays per complete flash) */
4144     flash_delay = 500 / appData.flashRate;
4145
4146     if (flipView) {
4147         x = lineGap + ((BOARD_WIDTH-1)-column) *
4148           (squareSize + lineGap);
4149         y = lineGap + row * (squareSize + lineGap);
4150     } else {
4151         x = lineGap + column * (squareSize + lineGap);
4152         y = lineGap + ((BOARD_HEIGHT-1)-row) *
4153           (squareSize + lineGap);
4154     }
4155
4156     if(twoBoards && partnerUp) x += hOffset; // [HGM] dual: draw second board
4157
4158     square_color = SquareColor(row, column);
4159
4160     if ( // [HGM] holdings: blank out area between board and holdings
4161                  column == BOARD_LEFT-1 ||  column == BOARD_RGHT
4162               || (column == BOARD_LEFT-2 && row < BOARD_HEIGHT-gameInfo.holdingsSize)
4163                   || (column == BOARD_RGHT+1 && row >= gameInfo.holdingsSize) ) {
4164                         BlankSquare(x, y, 2, EmptySquare, xBoardWindow, 1);
4165
4166                         // [HGM] print piece counts next to holdings
4167                         string[1] = NULLCHAR;
4168                         if (column == (flipView ? BOARD_LEFT-1 : BOARD_RGHT) && piece > 1 ) {
4169                             string[0] = '0' + piece;
4170                             XTextExtents(countFontStruct, string, 1, &direction,
4171                                  &font_ascent, &font_descent, &overall);
4172                             if (appData.monoMode) {
4173                                 XDrawImageString(xDisplay, xBoardWindow, countGC,
4174                                                  x + squareSize - overall.width - 2,
4175                                                  y + font_ascent + 1, string, 1);
4176                             } else {
4177                                 XDrawString(xDisplay, xBoardWindow, countGC,
4178                                             x + squareSize - overall.width - 2,
4179                                             y + font_ascent + 1, string, 1);
4180                             }
4181                         }
4182                         if (column == (flipView ? BOARD_RGHT : BOARD_LEFT-1) && piece > 1) {
4183                             string[0] = '0' + piece;
4184                             XTextExtents(countFontStruct, string, 1, &direction,
4185                                          &font_ascent, &font_descent, &overall);
4186                             if (appData.monoMode) {
4187                                 XDrawImageString(xDisplay, xBoardWindow, countGC,
4188                                                  x + 2, y + font_ascent + 1, string, 1);
4189                             } else {
4190                                 XDrawString(xDisplay, xBoardWindow, countGC,
4191                                             x + 2, y + font_ascent + 1, string, 1);
4192                             }
4193                         }
4194     } else {
4195             if (piece == EmptySquare || appData.blindfold) {
4196                         BlankSquare(x, y, square_color, piece, xBoardWindow, 1);
4197             } else {
4198                         drawfunc = ChooseDrawFunc();
4199
4200                         if (do_flash && appData.flashCount > 0) {
4201                             for (i=0; i<appData.flashCount; ++i) {
4202                                         drawfunc(piece, square_color, x, y, xBoardWindow);
4203                                         XSync(xDisplay, False);
4204                                         do_flash_delay(flash_delay);
4205
4206                                         BlankSquare(x, y, square_color, piece, xBoardWindow, 1);
4207                                         XSync(xDisplay, False);
4208                                         do_flash_delay(flash_delay);
4209                             }
4210                         }
4211                         drawfunc(piece, square_color, x, y, xBoardWindow);
4212         }
4213         }
4214
4215     string[1] = NULLCHAR;
4216     if (appData.showCoords && row == (flipView ? BOARD_HEIGHT-1 : 0)
4217                 && column >= BOARD_LEFT && column < BOARD_RGHT) {
4218         string[0] = 'a' + column - BOARD_LEFT;
4219         XTextExtents(coordFontStruct, string, 1, &direction,
4220                      &font_ascent, &font_descent, &overall);
4221         if (appData.monoMode) {
4222             XDrawImageString(xDisplay, xBoardWindow, coordGC,
4223                              x + squareSize - overall.width - 2,
4224                              y + squareSize - font_descent - 1, string, 1);
4225         } else {
4226             XDrawString(xDisplay, xBoardWindow, coordGC,
4227                         x + squareSize - overall.width - 2,
4228                         y + squareSize - font_descent - 1, string, 1);
4229         }
4230     }
4231     if (appData.showCoords && column == (flipView ? BOARD_RGHT-1 : BOARD_LEFT)) {
4232         string[0] = ONE + row;
4233         XTextExtents(coordFontStruct, string, 1, &direction,
4234                      &font_ascent, &font_descent, &overall);
4235         if (appData.monoMode) {
4236             XDrawImageString(xDisplay, xBoardWindow, coordGC,
4237                              x + 2, y + font_ascent + 1, string, 1);
4238         } else {
4239             XDrawString(xDisplay, xBoardWindow, coordGC,
4240                         x + 2, y + font_ascent + 1, string, 1);
4241         }
4242     }
4243     if(!partnerUp && marker[row][column]) {
4244         if(appData.monoMode) {
4245             XFillArc(xDisplay, xBoardWindow, marker[row][column] == 2 ? darkSquareGC : lightSquareGC,
4246                     x + squareSize/4, y+squareSize/4, squareSize/2, squareSize/2, 0, 64*360);
4247             XDrawArc(xDisplay, xBoardWindow, marker[row][column] == 2 ? lightSquareGC : darkSquareGC,
4248                     x + squareSize/4, y+squareSize/4, squareSize/2, squareSize/2, 0, 64*360);
4249         } else
4250         XFillArc(xDisplay, xBoardWindow, marker[row][column] == 2 ? prelineGC : highlineGC,
4251                 x + squareSize/4, y+squareSize/4, squareSize/2, squareSize/2, 0, 64*360);
4252     }
4253 }
4254
4255 double
4256 Fraction (int x, int start, int stop)
4257 {
4258    double f = ((double) x - start)/(stop - start);
4259    if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
4260    return f;
4261 }
4262
4263 static WindowPlacement wpNew;
4264
4265 void
4266 CoDrag (Widget sh, WindowPlacement *wp)
4267 {
4268     Arg args[16];
4269     int j=0, touch=0, fudge = 2;
4270     GetActualPlacement(sh, wp);
4271     if(abs(wpMain.x + wpMain.width + 2*frameX - wp->x)         < fudge) touch = 1; else // right touch
4272     if(abs(wp->x + wp->width + 2*frameX - wpMain.x)            < fudge) touch = 2; else // left touch
4273     if(abs(wpMain.y + wpMain.height + frameX + frameY - wp->y) < fudge) touch = 3; else // bottom touch
4274     if(abs(wp->y + wp->height + frameX + frameY - wpMain.y)    < fudge) touch = 4;      // top touch
4275     if(!touch ) return; // only windows that touch co-move
4276     if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
4277         int heightInc = wpNew.height - wpMain.height;
4278         double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
4279         double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
4280         wp->y += fracTop * heightInc;
4281         heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
4282         if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
4283     } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
4284         int widthInc = wpNew.width - wpMain.width;
4285         double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
4286         double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
4287         wp->y += fracLeft * widthInc;
4288         widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
4289         if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
4290     }
4291     wp->x += wpNew.x - wpMain.x;
4292     wp->y += wpNew.y - wpMain.y;
4293     if(touch == 1) wp->x += wpNew.width - wpMain.width; else
4294     if(touch == 3) wp->y += wpNew.height - wpMain.height;
4295     XtSetArg(args[j], XtNx, wp->x); j++;
4296     XtSetArg(args[j], XtNy, wp->y); j++;
4297     XtSetValues(sh, args, j);
4298 }
4299
4300 static XtIntervalId delayedDragID = 0;
4301
4302 void
4303 DragProc ()
4304 {
4305         GetActualPlacement(shellWidget, &wpNew);
4306         if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
4307            wpNew.width == wpMain.width && wpNew.height == wpMain.height) // not sized
4308             return; // false alarm
4309         if(EngineOutputIsUp()) CoDrag(engineOutputShell, &wpEngineOutput);
4310         if(MoveHistoryIsUp()) CoDrag(shells[7], &wpMoveHistory);
4311         if(EvalGraphIsUp()) CoDrag(evalGraphShell, &wpEvalGraph);
4312         if(GameListIsUp()) CoDrag(gameListShell, &wpGameList);
4313         wpMain = wpNew;
4314         XDrawPosition(boardWidget, True, NULL);
4315         delayedDragID = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
4316 }
4317
4318
4319 void
4320 DelayedDrag ()
4321 {
4322     if(delayedDragID) XtRemoveTimeOut(delayedDragID); // cancel pending
4323     delayedDragID =
4324       XtAppAddTimeOut(appContext, 50, (XtTimerCallbackProc) DragProc, (XtPointer) 0); // and schedule new one 50 msec later
4325 }
4326
4327 /* Why is this needed on some versions of X? */
4328 void
4329 EventProc (Widget widget, caddr_t unused, XEvent *event)
4330 {
4331     if (!XtIsRealized(widget))
4332       return;
4333     switch (event->type) {
4334       case ConfigureNotify: // main window is being dragged: drag attached windows with it
4335         if(appData.useStickyWindows)
4336             DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
4337         break;
4338       case Expose:
4339         if (event->xexpose.count > 0) return;  /* no clipping is done */
4340         XDrawPosition(widget, True, NULL);
4341         if(twoBoards) { // [HGM] dual: draw other board in other orientation
4342             flipView = !flipView; partnerUp = !partnerUp;
4343             XDrawPosition(widget, True, NULL);
4344             flipView = !flipView; partnerUp = !partnerUp;
4345         }
4346         break;
4347       case MotionNotify:
4348         if(SeekGraphClick(Press, event->xbutton.x, event->xbutton.y, 1)) break;
4349       default:
4350         return;
4351     }
4352 }
4353 /* end why */
4354
4355 void
4356 DrawPosition (int fullRedraw, Board board)
4357 {
4358     XDrawPosition(boardWidget, fullRedraw, board);
4359 }
4360
4361 /* Returns 1 if there are "too many" differences between b1 and b2
4362    (i.e. more than 1 move was made) */
4363 static int
4364 too_many_diffs (Board b1, Board b2)
4365 {
4366     int i, j;
4367     int c = 0;
4368
4369     for (i=0; i<BOARD_HEIGHT; ++i) {
4370         for (j=0; j<BOARD_WIDTH; ++j) {
4371             if (b1[i][j] != b2[i][j]) {
4372                 if (++c > 4)    /* Castling causes 4 diffs */
4373                   return 1;
4374             }
4375         }
4376     }
4377     return 0;
4378 }
4379
4380 /* Matrix describing castling maneuvers */
4381 /* Row, ColRookFrom, ColKingFrom, ColRookTo, ColKingTo */
4382 static int castling_matrix[4][5] = {
4383     { 0, 0, 4, 3, 2 },          /* 0-0-0, white */
4384     { 0, 7, 4, 5, 6 },          /* 0-0,   white */
4385     { 7, 0, 4, 3, 2 },          /* 0-0-0, black */
4386     { 7, 7, 4, 5, 6 }           /* 0-0,   black */
4387 };
4388
4389 /* Checks whether castling occurred. If it did, *rrow and *rcol
4390    are set to the destination (row,col) of the rook that moved.
4391
4392    Returns 1 if castling occurred, 0 if not.
4393
4394    Note: Only handles a max of 1 castling move, so be sure
4395    to call too_many_diffs() first.
4396    */
4397 static int
4398 check_castle_draw (Board newb, Board oldb, int *rrow, int *rcol)
4399 {
4400     int i, *r, j;
4401     int match;
4402
4403     /* For each type of castling... */
4404     for (i=0; i<4; ++i) {
4405         r = castling_matrix[i];
4406
4407         /* Check the 4 squares involved in the castling move */
4408         match = 0;
4409         for (j=1; j<=4; ++j) {
4410             if (newb[r[0]][r[j]] == oldb[r[0]][r[j]]) {
4411                 match = 1;
4412                 break;
4413             }
4414         }
4415
4416         if (!match) {
4417             /* All 4 changed, so it must be a castling move */
4418             *rrow = r[0];
4419             *rcol = r[3];
4420             return 1;
4421         }
4422     }
4423     return 0;
4424 }
4425
4426 // [HGM] seekgraph: some low-level drawing routines cloned from xevalgraph
4427 void
4428 DrawSeekAxis (int x, int y, int xTo, int yTo)
4429 {
4430       XDrawLine(xDisplay, xBoardWindow, lineGC, x, y, xTo, yTo);
4431 }
4432
4433 void
4434 DrawSeekBackground (int left, int top, int right, int bottom)
4435 {
4436     XFillRectangle(xDisplay, xBoardWindow, lightSquareGC, left, top, right-left, bottom-top);
4437 }
4438
4439 void
4440 DrawSeekText (char *buf, int x, int y)
4441 {
4442     XDrawString(xDisplay, xBoardWindow, coordGC, x, y+4, buf, strlen(buf));
4443 }
4444
4445 void
4446 DrawSeekDot (int x, int y, int colorNr)
4447 {
4448     int square = colorNr & 0x80;
4449     GC color;
4450     colorNr &= 0x7F;
4451     color = colorNr == 0 ? prelineGC : colorNr == 1 ? darkSquareGC : highlineGC;
4452     if(square)
4453         XFillRectangle(xDisplay, xBoardWindow, color,
4454                 x-squareSize/9, y-squareSize/9, 2*squareSize/9, 2*squareSize/9);
4455     else
4456         XFillArc(xDisplay, xBoardWindow, color,
4457                 x-squareSize/8, y-squareSize/8, squareSize/4, squareSize/4, 0, 64*360);
4458 }
4459
4460 static int damage[2][BOARD_RANKS][BOARD_FILES];
4461
4462 /*
4463  * event handler for redrawing the board
4464  */
4465 void
4466 XDrawPosition (Widget w, int repaint, Board board)
4467 {
4468     int i, j, do_flash;
4469     static int lastFlipView = 0;
4470     static int lastBoardValid[2] = {0, 0};
4471     static Board lastBoard[2];
4472     Arg args[16];
4473     int rrow, rcol;
4474     int nr = twoBoards*partnerUp;
4475
4476     if(DrawSeekGraph()) return; // [HGM] seekgraph: suppress any drawing if seek graph up
4477
4478     if (board == NULL) {
4479         if (!lastBoardValid[nr]) return;
4480         board = lastBoard[nr];
4481     }
4482     if (!lastBoardValid[nr] || (nr == 0 && lastFlipView != flipView)) {
4483         XtSetArg(args[0], XtNleftBitmap, (flipView ? xMarkPixmap : None));
4484         XtSetValues(XtNameToWidget(menuBarWidget, "menuView.Flip View"),
4485                     args, 1);
4486     }
4487
4488     /*
4489      * It would be simpler to clear the window with XClearWindow()
4490      * but this causes a very distracting flicker.
4491      */
4492
4493     if (!repaint && lastBoardValid[nr] && (nr == 1 || lastFlipView == flipView)) {
4494
4495         if ( lineGap && IsDrawArrowEnabled())
4496             XDrawSegments(xDisplay, xBoardWindow, lineGC,
4497                         gridSegments, BOARD_HEIGHT + BOARD_WIDTH + 2);
4498
4499         /* If too much changes (begin observing new game, etc.), don't
4500            do flashing */
4501         do_flash = too_many_diffs(board, lastBoard[nr]) ? 0 : 1;
4502
4503         /* Special check for castling so we don't flash both the king
4504            and the rook (just flash the king). */
4505         if (do_flash) {
4506             if (check_castle_draw(board, lastBoard[nr], &rrow, &rcol)) {
4507                 /* Draw rook with NO flashing. King will be drawn flashing later */
4508                 DrawSquare(rrow, rcol, board[rrow][rcol], 0);
4509                 lastBoard[nr][rrow][rcol] = board[rrow][rcol];
4510             }
4511         }
4512
4513         /* First pass -- Draw (newly) empty squares and repair damage.
4514            This prevents you from having a piece show up twice while it
4515            is flashing on its new square */
4516         for (i = 0; i < BOARD_HEIGHT; i++)
4517           for (j = 0; j < BOARD_WIDTH; j++)
4518             if ((board[i][j] != lastBoard[nr][i][j] && board[i][j] == EmptySquare)
4519                 || damage[nr][i][j]) {
4520                 DrawSquare(i, j, board[i][j], 0);
4521                 damage[nr][i][j] = False;
4522             }
4523
4524         /* Second pass -- Draw piece(s) in new position and flash them */
4525         for (i = 0; i < BOARD_HEIGHT; i++)
4526           for (j = 0; j < BOARD_WIDTH; j++)
4527             if (board[i][j] != lastBoard[nr][i][j]) {
4528                 DrawSquare(i, j, board[i][j], do_flash);
4529             }
4530     } else {
4531         if (lineGap > 0)
4532           XDrawSegments(xDisplay, xBoardWindow, lineGC,
4533                         twoBoards & partnerUp ? secondSegments : // [HGM] dual
4534                         gridSegments, BOARD_HEIGHT + BOARD_WIDTH + 2);
4535
4536         for (i = 0; i < BOARD_HEIGHT; i++)
4537           for (j = 0; j < BOARD_WIDTH; j++) {
4538               DrawSquare(i, j, board[i][j], 0);
4539               damage[nr][i][j] = False;
4540           }
4541     }
4542
4543     CopyBoard(lastBoard[nr], board);
4544     lastBoardValid[nr] = 1;
4545   if(nr == 0) { // [HGM] dual: no highlights on second board yet
4546     lastFlipView = flipView;
4547
4548     /* Draw highlights */
4549     if (pm1X >= 0 && pm1Y >= 0) {
4550       drawHighlight(pm1X, pm1Y, prelineGC);
4551     }
4552     if (pm2X >= 0 && pm2Y >= 0) {
4553       drawHighlight(pm2X, pm2Y, prelineGC);
4554     }
4555     if (hi1X >= 0 && hi1Y >= 0) {
4556       drawHighlight(hi1X, hi1Y, highlineGC);
4557     }
4558     if (hi2X >= 0 && hi2Y >= 0) {
4559       drawHighlight(hi2X, hi2Y, highlineGC);
4560     }
4561     DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y);
4562   }
4563     /* If piece being dragged around board, must redraw that too */
4564     DrawDragPiece();
4565
4566     XSync(xDisplay, False);
4567 }
4568
4569
4570 /*
4571  * event handler for redrawing the board
4572  */
4573 void
4574 DrawPositionProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
4575 {
4576     XDrawPosition(w, True, NULL);
4577 }
4578
4579
4580 /*
4581  * event handler for parsing user moves
4582  */
4583 // [HGM] This routine will need quite some reworking. Although the backend still supports the old
4584 //       way of doing things, by calling UserMoveEvent() to test the legality of the move and then perform
4585 //       it at the end, and doing all kind of preliminary tests here (e.g. to weed out self-captures), it
4586 //       should be made to use the new way, of calling UserMoveTest early  to determine the legality of the
4587 //       move, (which will weed out the illegal selfcaptures and moves into the holdings, and flag promotions),
4588 //       and at the end FinishMove() to perform the move after optional promotion popups.
4589 //       For now I patched it to allow self-capture with King, and suppress clicks between board and holdings.
4590 void
4591 HandleUserMove (Widget w, XEvent *event, String *prms, Cardinal *nprms)
4592 {
4593     if (w != boardWidget || errorExitStatus != -1) return;
4594     if(nprms) shiftKey = !strcmp(prms[0], "1");
4595
4596     if (promotionUp) {
4597         if (event->type == ButtonPress) {
4598             XtPopdown(promotionShell);
4599             XtDestroyWidget(promotionShell);
4600             promotionUp = False;
4601             ClearHighlights();
4602             fromX = fromY = -1;
4603         } else {
4604             return;
4605         }
4606     }
4607
4608     // [HGM] mouse: the rest of the mouse handler is moved to the backend, and called here
4609     if(event->type == ButtonPress)   LeftClick(Press,   event->xbutton.x, event->xbutton.y);
4610     if(event->type == ButtonRelease) LeftClick(Release, event->xbutton.x, event->xbutton.y);
4611 }
4612
4613 void
4614 AnimateUserMove (Widget w, XEvent *event, String *params, Cardinal *nParams)
4615 {
4616     if(!PromoScroll(event->xmotion.x, event->xmotion.y))
4617     DragPieceMove(event->xmotion.x, event->xmotion.y);
4618 }
4619
4620 void
4621 HandlePV (Widget w, XEvent * event, String * params, Cardinal * nParams)
4622 {   // [HGM] pv: walk PV
4623     MovePV(event->xmotion.x, event->xmotion.y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
4624 }
4625
4626 static int savedIndex;  /* gross that this is global */
4627
4628 void
4629 CommentClick (Widget w, XEvent * event, String * params, Cardinal * nParams)
4630 {
4631         String val;
4632         XawTextPosition index, dummy;
4633         Arg arg;
4634
4635         XawTextGetSelectionPos(w, &index, &dummy);
4636         XtSetArg(arg, XtNstring, &val);
4637         XtGetValues(w, &arg, 1);
4638         ReplaceComment(savedIndex, val);
4639         if(savedIndex != currentMove) ToNrEvent(savedIndex);
4640         LoadVariation( index, val ); // [HGM] also does the actual moving to it, now
4641 }
4642
4643 void
4644 EditCommentPopUp (int index, char *title, char *text)
4645 {
4646     savedIndex = index;
4647     if (text == NULL) text = "";
4648     NewCommentPopup(title, text, index);
4649 }
4650
4651 void
4652 ICSInputBoxPopUp ()
4653 {
4654     InputBoxPopup();
4655 }
4656
4657 extern Option boxOptions[];
4658
4659 void
4660 ICSInputSendText ()
4661 {
4662     Widget edit;
4663     int j;
4664     Arg args[16];
4665     String val;
4666
4667     edit = boxOptions[0].handle;
4668     j = 0;
4669     XtSetArg(args[j], XtNstring, &val); j++;
4670     XtGetValues(edit, args, j);
4671     SaveInHistory(val);
4672     SendMultiLineToICS(val);
4673     XtCallActionProc(edit, "select-all", NULL, NULL, 0);
4674     XtCallActionProc(edit, "kill-selection", NULL, NULL, 0);
4675 }
4676
4677 void
4678 ICSInputBoxPopDown ()
4679 {
4680     PopDown(4);
4681 }
4682
4683 void
4684 CommentPopUp (char *title, char *text)
4685 {
4686     savedIndex = currentMove; // [HGM] vari
4687     NewCommentPopup(title, text, currentMove);
4688 }
4689
4690 void
4691 CommentPopDown ()
4692 {
4693     PopDown(1);
4694 }
4695
4696 static char *openName;
4697 FILE *openFP;
4698
4699 void
4700 DelayedLoad ()
4701 {
4702   (void) (*fileProc)(openFP, 0, openName);
4703 }
4704
4705 void
4706 FileNamePopUp (char *label, char *def, char *filter, FileProc proc, char *openMode)
4707 {
4708     fileProc = proc;            /* I can't see a way not */
4709     fileOpenMode = openMode;    /*   to use globals here */
4710     {   // [HGM] use file-selector dialog stolen from Ghostview
4711         int index; // this is not supported yet
4712         if(openFP = XsraSelFile(shellWidget, label, NULL, NULL, _("could not open: "),
4713                            (def[0] ? def : NULL), filter, openMode, NULL, &openName))
4714           // [HGM] delay to give expose event opportunity to redraw board after browser-dialog popdown before lengthy load starts
4715           ScheduleDelayedEvent(&DelayedLoad, 50);
4716     }
4717 }
4718
4719 void
4720 FileNamePopDown ()
4721 {
4722     if (!filenameUp) return;
4723     XtPopdown(fileNameShell);
4724     XtDestroyWidget(fileNameShell);
4725     filenameUp = False;
4726     ModeHighlight();
4727 }
4728
4729 void
4730 FileNameCallback (Widget w, XtPointer client_data, XtPointer call_data)
4731 {
4732     String name;
4733     Arg args[16];
4734
4735     XtSetArg(args[0], XtNlabel, &name);
4736     XtGetValues(w, args, 1);
4737
4738     if (strcmp(name, _("cancel")) == 0) {
4739         FileNamePopDown();
4740         return;
4741     }
4742
4743     FileNameAction(w, NULL, NULL, NULL);
4744 }
4745
4746 void
4747 FileNameAction (Widget w, XEvent *event, String *prms, Cardinal *nprms)
4748 {
4749     char buf[MSG_SIZ];
4750     String name;
4751     FILE *f;
4752     char *p, *fullname;
4753     int index;
4754
4755     name = XawDialogGetValueString(w = XtParent(w));
4756
4757     if ((name != NULL) && (*name != NULLCHAR)) {
4758         safeStrCpy(buf, name, sizeof(buf)/sizeof(buf[0]) );
4759         XtPopdown(w = XtParent(XtParent(w)));
4760         XtDestroyWidget(w);
4761         filenameUp = False;
4762
4763         p = strrchr(buf, ' ');
4764         if (p == NULL) {
4765             index = 0;
4766         } else {
4767             *p++ = NULLCHAR;
4768             index = atoi(p);
4769         }
4770         fullname = ExpandPathName(buf);
4771         if (!fullname) {
4772             ErrorPopUp(_("Error"), _("Can't open file"), FALSE);
4773         }
4774         else {
4775             f = fopen(fullname, fileOpenMode);
4776             if (f == NULL) {
4777                 DisplayError(_("Failed to open file"), errno);
4778             } else {
4779                 (void) (*fileProc)(f, index, buf);
4780             }
4781         }
4782         ModeHighlight();
4783         return;
4784     }
4785
4786     XtPopdown(w = XtParent(XtParent(w)));
4787     XtDestroyWidget(w);
4788     filenameUp = False;
4789     ModeHighlight();
4790 }
4791
4792 void
4793 PromotionPopUp ()
4794 {
4795     Arg args[16];
4796     Widget dialog, layout;
4797     Position x, y;
4798     Dimension bw_width, pw_width;
4799     int j;
4800     char *PromoChars = "wglcqrbnkac+=\0";
4801
4802     j = 0;
4803     XtSetArg(args[j], XtNwidth, &bw_width); j++;
4804     XtGetValues(boardWidget, args, j);
4805
4806     j = 0;
4807     XtSetArg(args[j], XtNresizable, True); j++;
4808     XtSetArg(args[j], XtNtitle, XtNewString(_("Promotion"))); j++;
4809     promotionShell =
4810       XtCreatePopupShell("Promotion", transientShellWidgetClass,
4811                          shellWidget, args, j);
4812     layout =
4813       XtCreateManagedWidget(layoutName, formWidgetClass, promotionShell,
4814                             layoutArgs, XtNumber(layoutArgs));
4815
4816     j = 0;
4817     XtSetArg(args[j], XtNlabel, _("Promote to what?")); j++;
4818     XtSetArg(args[j], XtNborderWidth, 0); j++;
4819     dialog = XtCreateManagedWidget("promotion", dialogWidgetClass,
4820                                    layout, args, j);
4821
4822   if(gameInfo.variant != VariantShogi) {
4823    if(gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove)) {
4824       XawDialogAddButton(dialog, _("Warlord"), PromotionCallback, PromoChars + 0);
4825       XawDialogAddButton(dialog, _("General"), PromotionCallback, PromoChars + 1);
4826       XawDialogAddButton(dialog, _("Lieutenant"), PromotionCallback, PromoChars + 2);
4827       XawDialogAddButton(dialog, _("Captain"), PromotionCallback, PromoChars + 3);
4828     } else {
4829     XawDialogAddButton(dialog, _("Queen"), PromotionCallback, PromoChars + 4);
4830     XawDialogAddButton(dialog, _("Rook"), PromotionCallback, PromoChars + 5);
4831     XawDialogAddButton(dialog, _("Bishop"), PromotionCallback, PromoChars + 6);
4832     XawDialogAddButton(dialog, _("Knight"), PromotionCallback, PromoChars + 7);
4833     }
4834     if (!appData.testLegality || gameInfo.variant == VariantSuicide ||
4835         gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove) ||
4836         gameInfo.variant == VariantGiveaway) {
4837       XawDialogAddButton(dialog, _("King"), PromotionCallback, PromoChars + 8);
4838     }
4839     if(gameInfo.variant == VariantCapablanca ||
4840        gameInfo.variant == VariantGothic ||
4841        gameInfo.variant == VariantCapaRandom) {
4842       XawDialogAddButton(dialog, _("Archbishop"), PromotionCallback, PromoChars + 9);
4843       XawDialogAddButton(dialog, _("Chancellor"), PromotionCallback, PromoChars + 10);
4844     }
4845   } else // [HGM] shogi
4846   {
4847       XawDialogAddButton(dialog, _("Promote"), PromotionCallback, PromoChars + 11);
4848       XawDialogAddButton(dialog, _("Defer"), PromotionCallback, PromoChars + 12);
4849   }
4850     XawDialogAddButton(dialog, _("cancel"), PromotionCallback, PromoChars + 13);
4851
4852     XtRealizeWidget(promotionShell);
4853     CatchDeleteWindow(promotionShell, "PromotionPopDown");
4854
4855     j = 0;
4856     XtSetArg(args[j], XtNwidth, &pw_width); j++;
4857     XtGetValues(promotionShell, args, j);
4858
4859     XtTranslateCoords(boardWidget, (bw_width - pw_width) / 2,
4860                       lineGap + squareSize/3 +
4861                       ((toY == BOARD_HEIGHT-1) ^ (flipView) ?
4862                        0 : 6*(squareSize + lineGap)), &x, &y);
4863
4864     j = 0;
4865     XtSetArg(args[j], XtNx, x); j++;
4866     XtSetArg(args[j], XtNy, y); j++;
4867     XtSetValues(promotionShell, args, j);
4868
4869     XtPopup(promotionShell, XtGrabNone);
4870
4871     promotionUp = True;
4872 }
4873
4874 void
4875 PromotionPopDown ()
4876 {
4877     if (!promotionUp) return;
4878     XtPopdown(promotionShell);
4879     XtDestroyWidget(promotionShell);
4880     promotionUp = False;
4881 }
4882
4883 void
4884 PromotionCallback (Widget w, XtPointer client_data, XtPointer call_data)
4885 {
4886     int promoChar = * (const char *) client_data;
4887
4888     PromotionPopDown();
4889
4890     if (fromX == -1) return;
4891
4892     if (! promoChar) {
4893         fromX = fromY = -1;
4894         ClearHighlights();
4895         return;
4896     }
4897     UserMoveEvent(fromX, fromY, toX, toY, promoChar);
4898
4899     if (!appData.highlightLastMove || gotPremove) ClearHighlights();
4900     if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
4901     fromX = fromY = -1;
4902 }
4903
4904
4905 void
4906 ErrorCallback (Widget w, XtPointer client_data, XtPointer call_data)
4907 {
4908     dialogError = errorUp = False;
4909     XtPopdown(w = XtParent(XtParent(XtParent(w))));
4910     XtDestroyWidget(w);
4911     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
4912 }
4913
4914
4915 void
4916 ErrorPopDown ()
4917 {
4918     if (!errorUp) return;
4919     dialogError = errorUp = False;
4920     XtPopdown(errorShell);
4921     XtDestroyWidget(errorShell);
4922     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
4923 }
4924
4925 void
4926 ErrorPopUp (char *title, char *label, int modal)
4927 {
4928     Arg args[16];
4929     Widget dialog, layout;
4930     Position x, y;
4931     int xx, yy;
4932     Window junk;
4933     Dimension bw_width, pw_width;
4934     Dimension pw_height;
4935     int i;
4936
4937     i = 0;
4938     XtSetArg(args[i], XtNresizable, True);  i++;
4939     XtSetArg(args[i], XtNtitle, title); i++;
4940     errorShell =
4941       XtCreatePopupShell("errorpopup", transientShellWidgetClass,
4942                          shellUp[0] ? (dialogError = modal = TRUE, shells[0]) : shellWidget, args, i);
4943     layout =
4944       XtCreateManagedWidget(layoutName, formWidgetClass, errorShell,
4945                             layoutArgs, XtNumber(layoutArgs));
4946
4947     i = 0;
4948     XtSetArg(args[i], XtNlabel, label); i++;
4949     XtSetArg(args[i], XtNborderWidth, 0); i++;
4950     dialog = XtCreateManagedWidget("dialog", dialogWidgetClass,
4951                                    layout, args, i);
4952
4953     XawDialogAddButton(dialog, _("ok"), ErrorCallback, (XtPointer) dialog);
4954
4955     XtRealizeWidget(errorShell);
4956     CatchDeleteWindow(errorShell, "ErrorPopDown");
4957
4958     i = 0;
4959     XtSetArg(args[i], XtNwidth, &bw_width);  i++;
4960     XtGetValues(boardWidget, args, i);
4961     i = 0;
4962     XtSetArg(args[i], XtNwidth, &pw_width);  i++;
4963     XtSetArg(args[i], XtNheight, &pw_height);  i++;
4964     XtGetValues(errorShell, args, i);
4965
4966 #ifdef NOTDEF
4967     /* This code seems to tickle an X bug if it is executed too soon
4968        after xboard starts up.  The coordinates get transformed as if
4969        the main window was positioned at (0, 0).
4970        */
4971     XtTranslateCoords(boardWidget, (bw_width - pw_width) / 2,
4972                       0 - pw_height + squareSize / 3, &x, &y);
4973 #else
4974     XTranslateCoordinates(xDisplay, XtWindow(boardWidget),
4975                           RootWindowOfScreen(XtScreen(boardWidget)),
4976                           (bw_width - pw_width) / 2,
4977                           0 - pw_height + squareSize / 3, &xx, &yy, &junk);
4978     x = xx;
4979     y = yy;
4980 #endif
4981     if (y < 0) y = 0; /*avoid positioning top offscreen*/
4982
4983     i = 0;
4984     XtSetArg(args[i], XtNx, x);  i++;
4985     XtSetArg(args[i], XtNy, y);  i++;
4986     XtSetValues(errorShell, args, i);
4987
4988     errorUp = True;
4989     XtPopup(errorShell, modal ? XtGrabExclusive : XtGrabNone);
4990 }
4991
4992 /* Disable all user input other than deleting the window */
4993 static int frozen = 0;
4994
4995 void
4996 FreezeUI ()
4997 {
4998   if (frozen) return;
4999   /* Grab by a widget that doesn't accept input */
5000   XtAddGrab(messageWidget, TRUE, FALSE);
5001   frozen = 1;
5002 }
5003
5004 /* Undo a FreezeUI */
5005 void
5006 ThawUI ()
5007 {
5008   if (!frozen) return;
5009   XtRemoveGrab(messageWidget);
5010   frozen = 0;
5011 }
5012
5013 char *
5014 ModeToWidgetName (GameMode mode)
5015 {
5016     switch (mode) {
5017       case BeginningOfGame:
5018         if (appData.icsActive)
5019           return "menuMode.ICS Client";
5020         else if (appData.noChessProgram ||
5021                  *appData.cmailGameName != NULLCHAR)
5022           return "menuMode.Edit Game";
5023         else
5024           return "menuMode.Machine Black";
5025       case MachinePlaysBlack:
5026         return "menuMode.Machine Black";
5027       case MachinePlaysWhite:
5028         return "menuMode.Machine White";
5029       case AnalyzeMode:
5030         return "menuMode.Analysis Mode";
5031       case AnalyzeFile:
5032         return "menuMode.Analyze File";
5033       case TwoMachinesPlay:
5034         return "menuMode.Two Machines";
5035       case EditGame:
5036         return "menuMode.Edit Game";
5037       case PlayFromGameFile:
5038         return "menuFile.Load Game";
5039       case EditPosition:
5040         return "menuMode.Edit Position";
5041       case Training:
5042         return "menuMode.Training";
5043       case IcsPlayingWhite:
5044       case IcsPlayingBlack:
5045       case IcsObserving:
5046       case IcsIdle:
5047       case IcsExamining:
5048         return "menuMode.ICS Client";
5049       default:
5050       case EndOfGame:
5051         return NULL;
5052     }
5053 }
5054
5055 void
5056 ModeHighlight ()
5057 {
5058     Arg args[16];
5059     static int oldPausing = FALSE;
5060     static GameMode oldmode = (GameMode) -1;
5061     char *wname;
5062
5063     if (!boardWidget || !XtIsRealized(boardWidget)) return;
5064
5065     if (pausing != oldPausing) {
5066         oldPausing = pausing;
5067         if (pausing) {
5068             XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5069         } else {
5070             XtSetArg(args[0], XtNleftBitmap, None);
5071         }
5072         XtSetValues(XtNameToWidget(menuBarWidget, "menuMode.Pause"),
5073                     args, 1);
5074
5075         if (appData.showButtonBar) {
5076           /* Always toggle, don't set.  Previous code messes up when
5077              invoked while the button is pressed, as releasing it
5078              toggles the state again. */
5079           {
5080             Pixel oldbg, oldfg;
5081             XtSetArg(args[0], XtNbackground, &oldbg);
5082             XtSetArg(args[1], XtNforeground, &oldfg);
5083             XtGetValues(XtNameToWidget(buttonBarWidget, PAUSE_BUTTON),
5084                         args, 2);
5085             XtSetArg(args[0], XtNbackground, oldfg);
5086             XtSetArg(args[1], XtNforeground, oldbg);
5087           }
5088           XtSetValues(XtNameToWidget(buttonBarWidget, PAUSE_BUTTON), args, 2);
5089         }
5090     }
5091
5092     wname = ModeToWidgetName(oldmode);
5093     if (wname != NULL) {
5094         XtSetArg(args[0], XtNleftBitmap, None);
5095         XtSetValues(XtNameToWidget(menuBarWidget, wname), args, 1);
5096     }
5097     wname = ModeToWidgetName(gameMode);
5098     if (wname != NULL) {
5099         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5100         XtSetValues(XtNameToWidget(menuBarWidget, wname), args, 1);
5101     }
5102     oldmode = gameMode;
5103     XtSetArg(args[0], XtNleftBitmap, matchMode && matchGame < appData.matchGames ? xMarkPixmap : None);
5104     XtSetValues(XtNameToWidget(menuBarWidget, "menuMode.Machine Match"), args, 1);
5105
5106     /* Maybe all the enables should be handled here, not just this one */
5107     XtSetSensitive(XtNameToWidget(menuBarWidget, "menuMode.Training"),
5108                    gameMode == Training || gameMode == PlayFromGameFile);
5109 }
5110
5111
5112 /*
5113  * Button/menu procedures
5114  */
5115 int
5116 LoadGamePopUp (FILE *f, int gameNumber, char *title)
5117 {
5118     cmailMsgLoaded = FALSE;
5119     if (gameNumber == 0) {
5120         int error = GameListBuild(f);
5121         if (error) {
5122             DisplayError(_("Cannot build game list"), error);
5123         } else if (!ListEmpty(&gameList) &&
5124                    ((ListGame *) gameList.tailPred)->number > 1) {
5125             GameListPopUp(f, title);
5126             return TRUE;
5127         }
5128         GameListDestroy();
5129         gameNumber = 1;
5130     }
5131     return LoadGame(f, gameNumber, title, FALSE);
5132 }
5133
5134 void
5135 LoadGameProc ()
5136 {
5137     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
5138         Reset(FALSE, TRUE);
5139     }
5140     FileNamePopUp(_("Load game file name?"), "", ".pgn .game", LoadGamePopUp, "rb");
5141 }
5142
5143 void
5144 LoadNextGameProc ()
5145 {
5146     ReloadGame(1);
5147 }
5148
5149 void
5150 LoadPrevGameProc ()
5151 {
5152     ReloadGame(-1);
5153 }
5154
5155 void
5156 ReloadGameProc ()
5157 {
5158     ReloadGame(0);
5159 }
5160
5161 void
5162 LoadNextPositionProc ()
5163 {
5164     ReloadPosition(1);
5165 }
5166
5167 void
5168 LoadPrevPositionProc ()
5169 {
5170     ReloadPosition(-1);
5171 }
5172
5173 void
5174 ReloadPositionProc ()
5175 {
5176     ReloadPosition(0);
5177 }
5178
5179 void
5180 LoadPositionProc() 
5181 {
5182     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
5183         Reset(FALSE, TRUE);
5184     }
5185     FileNamePopUp(_("Load position file name?"), "", ".fen .epd .pos", LoadPosition, "rb");
5186 }
5187
5188 void
5189 SaveGameProc ()
5190 {
5191     FileNamePopUp(_("Save game file name?"),
5192                   DefaultFileName(appData.oldSaveStyle ? "game" : "pgn"),
5193                   appData.oldSaveStyle ? ".game" : ".pgn",
5194                   SaveGame, "a");
5195 }
5196
5197 void
5198 SavePositionProc ()
5199 {
5200     FileNamePopUp(_("Save position file name?"),
5201                   DefaultFileName(appData.oldSaveStyle ? "pos" : "fen"),
5202                   appData.oldSaveStyle ? ".pos" : ".fen",
5203                   SavePosition, "a");
5204 }
5205
5206 void
5207 ReloadCmailMsgProc ()
5208 {
5209     ReloadCmailMsgEvent(FALSE);
5210 }
5211
5212 /* this variable is shared between CopyPositionProc and SendPositionSelection */
5213 char *selected_fen_position=NULL;
5214
5215 Boolean
5216 SendPositionSelection (Widget w, Atom *selection, Atom *target,
5217                        Atom *type_return, XtPointer *value_return,
5218                        unsigned long *length_return, int *format_return)
5219 {
5220   char *selection_tmp;
5221
5222   if (!selected_fen_position) return False; /* should never happen */
5223   if (*target == XA_STRING || *target == XA_UTF8_STRING(xDisplay)){
5224     /* note: since no XtSelectionDoneProc was registered, Xt will
5225      * automatically call XtFree on the value returned.  So have to
5226      * make a copy of it allocated with XtMalloc */
5227     selection_tmp= XtMalloc(strlen(selected_fen_position)+16);
5228     safeStrCpy(selection_tmp, selected_fen_position, strlen(selected_fen_position)+16 );
5229
5230     *value_return=selection_tmp;
5231     *length_return=strlen(selection_tmp);
5232     *type_return=*target;
5233     *format_return = 8; /* bits per byte */
5234     return True;
5235   } else if (*target == XA_TARGETS(xDisplay)) {
5236     Atom *targets_tmp = (Atom *) XtMalloc(2 * sizeof(Atom));
5237     targets_tmp[0] = XA_UTF8_STRING(xDisplay);
5238     targets_tmp[1] = XA_STRING;
5239     *value_return = targets_tmp;
5240     *type_return = XA_ATOM;
5241     *length_return = 2;
5242 #if 0
5243     // This code leads to a read of value_return out of bounds on 64-bit systems.
5244     // Other code which I have seen always sets *format_return to 32 independent of
5245     // sizeof(Atom) without adjusting *length_return. For instance see TextConvertSelection()
5246     // at http://cgit.freedesktop.org/xorg/lib/libXaw/tree/src/Text.c -- BJ
5247     *format_return = 8 * sizeof(Atom);
5248     if (*format_return > 32) {
5249       *length_return *= *format_return / 32;
5250       *format_return = 32;
5251     }
5252 #else
5253     *format_return = 32;
5254 #endif
5255     return True;
5256   } else {
5257     return False;
5258   }
5259 }
5260
5261 /* note: when called from menu all parameters are NULL, so no clue what the
5262  * Widget which was clicked on was, or what the click event was
5263  */
5264 void
5265 CopyPositionProc ()
5266 {
5267     /*
5268      * Set both PRIMARY (the selection) and CLIPBOARD, since we don't
5269      * have a notion of a position that is selected but not copied.
5270      * See http://www.freedesktop.org/wiki/Specifications/ClipboardsWiki
5271      */
5272     if(gameMode == EditPosition) EditPositionDone(TRUE);
5273     if (selected_fen_position) free(selected_fen_position);
5274     selected_fen_position = (char *)PositionToFEN(currentMove, NULL);
5275     if (!selected_fen_position) return;
5276     XtOwnSelection(menuBarWidget, XA_PRIMARY,
5277                    CurrentTime,
5278                    SendPositionSelection,
5279                    NULL/* lose_ownership_proc */ ,
5280                    NULL/* transfer_done_proc */);
5281     XtOwnSelection(menuBarWidget, XA_CLIPBOARD(xDisplay),
5282                    CurrentTime,
5283                    SendPositionSelection,
5284                    NULL/* lose_ownership_proc */ ,
5285                    NULL/* transfer_done_proc */);
5286 }
5287
5288 void
5289 CopyFENToClipboard ()
5290 { // wrapper to make call from back-end possible
5291   CopyPositionProc();
5292 }
5293
5294 /* function called when the data to Paste is ready */
5295 static void
5296 PastePositionCB (Widget w, XtPointer client_data, Atom *selection,
5297                  Atom *type, XtPointer value, unsigned long *len, int *format)
5298 {
5299   char *fenstr=value;
5300   if (value==NULL || *len==0) return; /* nothing had been selected to copy */
5301   fenstr[*len]='\0'; /* normally this string is terminated, but be safe */
5302   EditPositionPasteFEN(fenstr);
5303   XtFree(value);
5304 }
5305
5306 /* called when Paste Position button is pressed,
5307  * all parameters will be NULL */
5308 void
5309 PastePositionProc ()
5310 {
5311     XtGetSelectionValue(menuBarWidget,
5312       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
5313       /* (XtSelectionCallbackProc) */ PastePositionCB,
5314       NULL, /* client_data passed to PastePositionCB */
5315
5316       /* better to use the time field from the event that triggered the
5317        * call to this function, but that isn't trivial to get
5318        */
5319       CurrentTime
5320     );
5321     return;
5322 }
5323
5324 static Boolean
5325 SendGameSelection (Widget w, Atom *selection, Atom *target,
5326                    Atom *type_return, XtPointer *value_return,
5327                    unsigned long *length_return, int *format_return)
5328 {
5329   char *selection_tmp;
5330
5331   if (*target == XA_STRING || *target == XA_UTF8_STRING(xDisplay)){
5332     FILE* f = fopen(gameCopyFilename, "r");
5333     long len;
5334     size_t count;
5335     if (f == NULL) return False;
5336     fseek(f, 0, 2);
5337     len = ftell(f);
5338     rewind(f);
5339     selection_tmp = XtMalloc(len + 1);
5340     count = fread(selection_tmp, 1, len, f);
5341     fclose(f);
5342     if (len != count) {
5343       XtFree(selection_tmp);
5344       return False;
5345     }
5346     selection_tmp[len] = NULLCHAR;
5347     *value_return = selection_tmp;
5348     *length_return = len;
5349     *type_return = *target;
5350     *format_return = 8; /* bits per byte */
5351     return True;
5352   } else if (*target == XA_TARGETS(xDisplay)) {
5353     Atom *targets_tmp = (Atom *) XtMalloc(2 * sizeof(Atom));
5354     targets_tmp[0] = XA_UTF8_STRING(xDisplay);
5355     targets_tmp[1] = XA_STRING;
5356     *value_return = targets_tmp;
5357     *type_return = XA_ATOM;
5358     *length_return = 2;
5359 #if 0
5360     // This code leads to a read of value_return out of bounds on 64-bit systems.
5361     // Other code which I have seen always sets *format_return to 32 independent of
5362     // sizeof(Atom) without adjusting *length_return. For instance see TextConvertSelection()
5363     // at http://cgit.freedesktop.org/xorg/lib/libXaw/tree/src/Text.c -- BJ
5364     *format_return = 8 * sizeof(Atom);
5365     if (*format_return > 32) {
5366       *length_return *= *format_return / 32;
5367       *format_return = 32;
5368     }
5369 #else
5370     *format_return = 32;
5371 #endif
5372     return True;
5373   } else {
5374     return False;
5375   }
5376 }
5377
5378 void
5379 CopySomething ()
5380 {
5381   /*
5382    * Set both PRIMARY (the selection) and CLIPBOARD, since we don't
5383    * have a notion of a game that is selected but not copied.
5384    * See http://www.freedesktop.org/wiki/Specifications/ClipboardsWiki
5385    */
5386   XtOwnSelection(menuBarWidget, XA_PRIMARY,
5387                  CurrentTime,
5388                  SendGameSelection,
5389                  NULL/* lose_ownership_proc */ ,
5390                  NULL/* transfer_done_proc */);
5391   XtOwnSelection(menuBarWidget, XA_CLIPBOARD(xDisplay),
5392                  CurrentTime,
5393                  SendGameSelection,
5394                  NULL/* lose_ownership_proc */ ,
5395                  NULL/* transfer_done_proc */);
5396 }
5397
5398 /* note: when called from menu all parameters are NULL, so no clue what the
5399  * Widget which was clicked on was, or what the click event was
5400  */
5401 void
5402 CopyGameProc ()
5403 {
5404   int ret;
5405
5406   ret = SaveGameToFile(gameCopyFilename, FALSE);
5407   if (!ret) return;
5408
5409   CopySomething();
5410 }
5411
5412 void
5413 CopyGameListProc ()
5414 {
5415   if(!SaveGameListAsText(fopen(gameCopyFilename, "w"))) return;
5416   CopySomething();
5417 }
5418
5419 /* function called when the data to Paste is ready */
5420 static void
5421 PasteGameCB (Widget w, XtPointer client_data, Atom *selection,
5422              Atom *type, XtPointer value, unsigned long *len, int *format)
5423 {
5424   FILE* f;
5425   if (value == NULL || *len == 0) {
5426     return; /* nothing had been selected to copy */
5427   }
5428   f = fopen(gamePasteFilename, "w");
5429   if (f == NULL) {
5430     DisplayError(_("Can't open temp file"), errno);
5431     return;
5432   }
5433   fwrite(value, 1, *len, f);
5434   fclose(f);
5435   XtFree(value);
5436   LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
5437 }
5438
5439 /* called when Paste Game button is pressed,
5440  * all parameters will be NULL */
5441 void
5442 PasteGameProc ()
5443 {
5444     XtGetSelectionValue(menuBarWidget,
5445       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
5446       /* (XtSelectionCallbackProc) */ PasteGameCB,
5447       NULL, /* client_data passed to PasteGameCB */
5448
5449       /* better to use the time field from the event that triggered the
5450        * call to this function, but that isn't trivial to get
5451        */
5452       CurrentTime
5453     );
5454     return;
5455 }
5456
5457
5458 void
5459 AutoSaveGame ()
5460 {
5461     SaveGameProc();
5462 }
5463
5464
5465 void
5466 QuitProc ()
5467 {
5468     ExitEvent(0);
5469 }
5470
5471 void
5472 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
5473 {
5474     QuitProc();
5475 }
5476
5477 void
5478 AnalyzeModeProc ()
5479 {
5480     char buf[MSG_SIZ];
5481
5482     if (!first.analysisSupport) {
5483       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
5484       DisplayError(buf, 0);
5485       return;
5486     }
5487     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
5488     if (appData.icsActive) {
5489         if (gameMode != IcsObserving) {
5490           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
5491             DisplayError(buf, 0);
5492             /* secure check */
5493             if (appData.icsEngineAnalyze) {
5494                 if (appData.debugMode)
5495                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
5496                 ExitAnalyzeMode();
5497                 ModeHighlight();
5498             }
5499             return;
5500         }
5501         /* if enable, use want disable icsEngineAnalyze */
5502         if (appData.icsEngineAnalyze) {
5503                 ExitAnalyzeMode();
5504                 ModeHighlight();
5505                 return;
5506         }
5507         appData.icsEngineAnalyze = TRUE;
5508         if (appData.debugMode)
5509             fprintf(debugFP, _("ICS engine analyze starting... \n"));
5510     }
5511 #ifndef OPTIONSDIALOG
5512     if (!appData.showThinking)
5513       ShowThinkingProc();
5514 #endif
5515
5516     AnalyzeModeEvent();
5517 }
5518
5519 void
5520 AnalyzeFileProc ()
5521 {
5522     if (!first.analysisSupport) {
5523       char buf[MSG_SIZ];
5524       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
5525       DisplayError(buf, 0);
5526       return;
5527     }
5528 //    Reset(FALSE, TRUE);
5529 #ifndef OPTIONSDIALOG
5530     if (!appData.showThinking)
5531       ShowThinkingProc();
5532 #endif
5533     AnalyzeFileEvent();
5534 //    FileNamePopUp(_("File to analyze"), "", ".pgn .game", LoadGamePopUp, "rb");
5535     AnalysisPeriodicEvent(1);
5536 }
5537
5538 void
5539 MatchProc ()
5540 {
5541     MatchEvent(2);
5542 }
5543
5544 void
5545 EditCommentProc ()
5546 {
5547     Arg args[5];
5548     int j;
5549     if (PopDown(1)) { // popdown succesful
5550         j = 0;
5551         XtSetArg(args[j], XtNleftBitmap, None); j++;
5552         XtSetValues(XtNameToWidget(menuBarWidget, "menuEdit.Edit Comment"), args, j);
5553         XtSetValues(XtNameToWidget(menuBarWidget, "menuView.Show Comments"), args, j);
5554     } else // was not up
5555         EditCommentEvent();
5556 }
5557
5558 void
5559 IcsInputBoxProc ()
5560 {
5561     if (!PopDown(4)) ICSInputBoxPopUp();
5562 }
5563
5564 void
5565 AdjuWhiteProc ()
5566 {
5567     UserAdjudicationEvent(+1);
5568 }
5569
5570 void
5571 AdjuBlackProc ()
5572 {
5573     UserAdjudicationEvent(-1);
5574 }
5575
5576 void
5577 AdjuDrawProc ()
5578 {
5579     UserAdjudicationEvent(0);
5580 }
5581
5582 void
5583 EnterKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
5584 {
5585     if (shellUp[4] == True)
5586       ICSInputSendText();
5587 }
5588
5589 void
5590 UpKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
5591 {   // [HGM] input: let up-arrow recall previous line from history
5592     Widget edit;
5593     int j;
5594     Arg args[16];
5595     String val;
5596     XawTextBlock t;
5597
5598     if (!shellUp[4]) return;
5599     edit = boxOptions[0].handle;
5600     j = 0;
5601     XtSetArg(args[j], XtNstring, &val); j++;
5602     XtGetValues(edit, args, j);
5603     val = PrevInHistory(val);
5604     XtCallActionProc(edit, "select-all", NULL, NULL, 0);
5605     XtCallActionProc(edit, "kill-selection", NULL, NULL, 0);
5606     if(val) {
5607         t.ptr = val; t.firstPos = 0; t.length = strlen(val); t.format = XawFmt8Bit;
5608         XawTextReplace(edit, 0, 0, &t);
5609         XawTextSetInsertionPoint(edit, 9999);
5610     }
5611 }
5612
5613 void
5614 DownKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
5615 {   // [HGM] input: let down-arrow recall next line from history
5616     Widget edit;
5617     String val;
5618     XawTextBlock t;
5619
5620     if (!shellUp[4]) return;
5621     edit = boxOptions[0].handle;
5622     val = NextInHistory();
5623     XtCallActionProc(edit, "select-all", NULL, NULL, 0);
5624     XtCallActionProc(edit, "kill-selection", NULL, NULL, 0);
5625     if(val) {
5626         t.ptr = val; t.firstPos = 0; t.length = strlen(val); t.format = XawFmt8Bit;
5627         XawTextReplace(edit, 0, 0, &t);
5628         XawTextSetInsertionPoint(edit, 9999);
5629     }
5630 }
5631
5632 void
5633 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
5634 {
5635         if (!TempBackwardActive) {
5636                 TempBackwardActive = True;
5637                 BackwardEvent();
5638         }
5639 }
5640
5641 void
5642 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
5643 {
5644         /* Check to see if triggered by a key release event for a repeating key.
5645          * If so the next queued event will be a key press of the same key at the same time */
5646         if (XEventsQueued(xDisplay, QueuedAfterReading)) {
5647                 XEvent next;
5648                 XPeekEvent(xDisplay, &next);
5649                 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
5650                         next.xkey.keycode == event->xkey.keycode)
5651                                 return;
5652         }
5653     ForwardEvent();
5654         TempBackwardActive = False;
5655 }
5656
5657 void
5658 RevertProc ()
5659 {
5660     RevertEvent(False);
5661 }
5662
5663 void
5664 AnnotateProc ()
5665 {
5666     RevertEvent(True);
5667 }
5668
5669 void
5670 FlipViewProc ()
5671 {
5672     flipView = !flipView;
5673     DrawPosition(True, NULL);
5674 }
5675
5676 void
5677 PonderNextMoveProc ()
5678 {
5679     Arg args[16];
5680
5681     PonderNextMoveEvent(!appData.ponderNextMove);
5682 #ifndef OPTIONSDIALOG
5683     if (appData.ponderNextMove) {
5684         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5685     } else {
5686         XtSetArg(args[0], XtNleftBitmap, None);
5687     }
5688     XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Ponder Next Move"),
5689                 args, 1);
5690 #endif
5691 }
5692
5693 #ifndef OPTIONSDIALOG
5694 void
5695 AlwaysQueenProc ()
5696 {
5697     Arg args[16];
5698
5699     appData.alwaysPromoteToQueen = !appData.alwaysPromoteToQueen;
5700
5701     if (appData.alwaysPromoteToQueen) {
5702         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5703     } else {
5704         XtSetArg(args[0], XtNleftBitmap, None);
5705     }
5706     XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Always Queen"),
5707                 args, 1);
5708 }
5709
5710 void
5711 AnimateDraggingProc ()
5712 {
5713     Arg args[16];
5714
5715     appData.animateDragging = !appData.animateDragging;
5716
5717     if (appData.animateDragging) {
5718         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5719         CreateAnimVars();
5720     } else {
5721         XtSetArg(args[0], XtNleftBitmap, None);
5722     }
5723     XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Animate Dragging"),
5724                 args, 1);
5725 }
5726
5727 void
5728 AnimateMovingProc ()
5729 {
5730     Arg args[16];
5731
5732     appData.animate = !appData.animate;
5733
5734     if (appData.animate) {
5735         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5736         CreateAnimVars();
5737     } else {
5738         XtSetArg(args[0], XtNleftBitmap, None);
5739     }
5740     XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Animate Moving"),
5741                 args, 1);
5742 }
5743
5744 void
5745 AutoflagProc ()
5746 {
5747     Arg args[16];
5748
5749     appData.autoCallFlag = !appData.autoCallFlag;
5750
5751     if (appData.autoCallFlag) {
5752         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5753     } else {
5754         XtSetArg(args[0], XtNleftBitmap, None);
5755     }
5756     XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Auto Flag"),
5757                 args, 1);
5758 }
5759
5760 void
5761 AutoflipProc ()
5762 {
5763     Arg args[16];
5764
5765     appData.autoFlipView = !appData.autoFlipView;
5766
5767     if (appData.autoFlipView) {
5768         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5769     } else {
5770         XtSetArg(args[0], XtNleftBitmap, None);
5771     }
5772     XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Auto Flip View"),
5773                 args, 1);
5774 }
5775
5776 void
5777 BlindfoldProc ()
5778 {
5779     Arg args[16];
5780
5781     appData.blindfold = !appData.blindfold;
5782
5783     if (appData.blindfold) {
5784         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5785     } else {
5786         XtSetArg(args[0], XtNleftBitmap, None);
5787     }
5788     XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Blindfold"),
5789                 args, 1);
5790
5791     DrawPosition(True, NULL);
5792 }
5793
5794 void
5795 TestLegalityProc ()
5796 {
5797     Arg args[16];
5798
5799     appData.testLegality = !appData.testLegality;
5800
5801     if (appData.testLegality) {
5802         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5803     } else {
5804         XtSetArg(args[0], XtNleftBitmap, None);
5805     }
5806     XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Test Legality"),
5807                 args, 1);
5808 }
5809
5810
5811 void
5812 FlashMovesProc ()
5813 {
5814     Arg args[16];
5815
5816     if (appData.flashCount == 0) {
5817         appData.flashCount = 3;
5818     } else {
5819         appData.flashCount = -appData.flashCount;
5820     }
5821
5822     if (appData.flashCount > 0) {
5823         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5824     } else {
5825         XtSetArg(args[0], XtNleftBitmap, None);
5826     }
5827     XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Flash Moves"),
5828                 args, 1);
5829 }
5830
5831 #if HIGHDRAG
5832 void
5833 HighlightDraggingProc ()
5834 {
5835     Arg args[16];
5836
5837     appData.highlightDragging = !appData.highlightDragging;
5838
5839     if (appData.highlightDragging) {
5840         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5841     } else {
5842         XtSetArg(args[0], XtNleftBitmap, None);
5843     }
5844     XtSetValues(XtNameToWidget(menuBarWidget,
5845                                "menuOptions.Highlight Dragging"), args, 1);
5846 }
5847 #endif
5848
5849 void
5850 HighlightLastMoveProc ()
5851 {
5852     Arg args[16];
5853
5854     appData.highlightLastMove = !appData.highlightLastMove;
5855
5856     if (appData.highlightLastMove) {
5857         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5858     } else {
5859         XtSetArg(args[0], XtNleftBitmap, None);
5860     }
5861     XtSetValues(XtNameToWidget(menuBarWidget,
5862                                "menuOptions.Highlight Last Move"), args, 1);
5863 }
5864
5865 void
5866 HighlightArrowProc ()
5867 {
5868     Arg args[16];
5869
5870     appData.highlightMoveWithArrow = !appData.highlightMoveWithArrow;
5871
5872     if (appData.highlightMoveWithArrow) {
5873         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5874     } else {
5875         XtSetArg(args[0], XtNleftBitmap, None);
5876     }
5877     XtSetValues(XtNameToWidget(menuBarWidget,
5878                                "menuOptions.Arrow"), args, 1);
5879 }
5880
5881 #if 0
5882 void
5883 IcsAlarmProc ()
5884 {
5885     Arg args[16];
5886
5887     appData.icsAlarm = !appData.icsAlarm;
5888
5889     if (appData.icsAlarm) {
5890         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5891     } else {
5892         XtSetArg(args[0], XtNleftBitmap, None);
5893     }
5894     XtSetValues(XtNameToWidget(menuBarWidget,
5895                                "menuOptions.ICS Alarm"), args, 1);
5896 }
5897 #endif
5898
5899 void
5900 MoveSoundProc ()
5901 {
5902     Arg args[16];
5903
5904     appData.ringBellAfterMoves = !appData.ringBellAfterMoves;
5905
5906     if (appData.ringBellAfterMoves) {
5907         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5908     } else {
5909         XtSetArg(args[0], XtNleftBitmap, None);
5910     }
5911     XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Move Sound"),
5912                 args, 1);
5913 }
5914
5915 void
5916 OneClickProc ()
5917 {
5918     Arg args[16];
5919
5920     appData.oneClick = !appData.oneClick;
5921
5922     if (appData.oneClick) {
5923         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5924     } else {
5925         XtSetArg(args[0], XtNleftBitmap, None);
5926     }
5927     XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.OneClick"),
5928                 args, 1);
5929 }
5930
5931 void
5932 PeriodicUpdatesProc ()
5933 {
5934     Arg args[16];
5935
5936     PeriodicUpdatesEvent(!appData.periodicUpdates);
5937
5938     if (appData.periodicUpdates) {
5939         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5940     } else {
5941         XtSetArg(args[0], XtNleftBitmap, None);
5942     }
5943     XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Periodic Updates"),
5944                 args, 1);
5945 }
5946
5947 void
5948 PopupExitMessageProc ()
5949 {
5950     Arg args[16];
5951
5952     appData.popupExitMessage = !appData.popupExitMessage;
5953
5954     if (appData.popupExitMessage) {
5955         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5956     } else {
5957         XtSetArg(args[0], XtNleftBitmap, None);
5958     }
5959     XtSetValues(XtNameToWidget(menuBarWidget,
5960                                "menuOptions.Popup Exit Message"), args, 1);
5961 }
5962
5963 void
5964 PopupMoveErrorsProc ()
5965 {
5966     Arg args[16];
5967
5968     appData.popupMoveErrors = !appData.popupMoveErrors;
5969
5970     if (appData.popupMoveErrors) {
5971         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5972     } else {
5973         XtSetArg(args[0], XtNleftBitmap, None);
5974     }
5975     XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Popup Move Errors"),
5976                 args, 1);
5977 }
5978
5979 #if 0
5980 void
5981 PremoveProc ()
5982 {
5983     Arg args[16];
5984
5985     appData.premove = !appData.premove;
5986
5987     if (appData.premove) {
5988         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5989     } else {
5990         XtSetArg(args[0], XtNleftBitmap, None);
5991     }
5992     XtSetValues(XtNameToWidget(menuBarWidget,
5993                                "menuOptions.Premove"), args, 1);
5994 }
5995 #endif
5996
5997 void
5998 ShowCoordsProc ()
5999 {
6000     Arg args[16];
6001
6002     appData.showCoords = !appData.showCoords;
6003
6004     if (appData.showCoords) {
6005         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
6006     } else {
6007         XtSetArg(args[0], XtNleftBitmap, None);
6008     }
6009     XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Show Coords"),
6010                 args, 1);
6011
6012     DrawPosition(True, NULL);
6013 }
6014
6015 void
6016 ShowThinkingProc ()
6017 {
6018     appData.showThinking = !appData.showThinking; // [HGM] thinking: tken out of ShowThinkingEvent
6019     ShowThinkingEvent();
6020 }
6021
6022 void
6023 HideThinkingProc ()
6024 {
6025     Arg args[16];
6026
6027     appData.hideThinkingFromHuman = !appData.hideThinkingFromHuman; // [HGM] thinking: tken out of ShowThinkingEvent
6028     ShowThinkingEvent();
6029
6030     if (appData.hideThinkingFromHuman) {
6031         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
6032     } else {
6033         XtSetArg(args[0], XtNleftBitmap, None);
6034     }
6035     XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Hide Thinking"),
6036                 args, 1);
6037 }
6038 #endif
6039
6040 void
6041 SaveOnExitProc ()
6042 {
6043     Arg args[16];
6044
6045     saveSettingsOnExit = !saveSettingsOnExit;
6046
6047     if (saveSettingsOnExit) {
6048         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
6049     } else {
6050         XtSetArg(args[0], XtNleftBitmap, None);
6051     }
6052     XtSetValues(XtNameToWidget(menuBarWidget, "menuOptions.Save Settings on Exit"),
6053                 args, 1);
6054 }
6055
6056 void
6057 SaveSettingsProc ()
6058 {
6059      SaveSettings(settingsFileName);
6060 }
6061
6062 void
6063 InfoProc ()
6064 {
6065     char buf[MSG_SIZ];
6066     snprintf(buf, sizeof(buf), "xterm -e info --directory %s --directory . -f %s &",
6067             INFODIR, INFOFILE);
6068     system(buf);
6069 }
6070
6071 void
6072 ManInner (Widget w, XEvent *event, String *prms, Cardinal *nprms)
6073 {   // called as key binding
6074     char buf[MSG_SIZ];
6075     String name;
6076     if (nprms && *nprms > 0)
6077       name = prms[0];
6078     else
6079       name = "xboard";
6080     snprintf(buf, sizeof(buf), "xterm -e man %s &", name);
6081     system(buf);
6082 }
6083
6084 void
6085 ManProc ()
6086 {   // called from menu
6087     ManInner(NULL, NULL, NULL, NULL);
6088 }
6089
6090 void
6091 BugReportProc ()
6092 {
6093     char buf[MSG_SIZ];
6094     snprintf(buf, MSG_SIZ, "%s mailto:bug-xboard@gnu.org", appData.sysOpen);
6095     system(buf);
6096 }
6097
6098 void
6099 GuideProc ()
6100 {
6101     char buf[MSG_SIZ];
6102     snprintf(buf, MSG_SIZ, "%s http://www.gnu.org/software/xboard/user_guide/UserGuide.html", appData.sysOpen);
6103     system(buf);
6104 }
6105
6106 void
6107 HomePageProc ()
6108 {
6109     char buf[MSG_SIZ];
6110     snprintf(buf, MSG_SIZ, "%s http://www.gnu.org/software/xboard/", appData.sysOpen);
6111     system(buf);
6112 }
6113
6114 void
6115 NewsPageProc ()
6116 {
6117     char buf[MSG_SIZ];
6118     snprintf(buf, MSG_SIZ, "%s http://www.gnu.org/software/xboard/whats_new/portal.html", appData.sysOpen);
6119     system(buf);
6120 }
6121
6122 void
6123 AboutProc ()
6124 {
6125     char buf[2 * MSG_SIZ];
6126 #if ZIPPY
6127     char *zippy = _(" (with Zippy code)");
6128 #else
6129     char *zippy = "";
6130 #endif
6131     snprintf(buf, sizeof(buf), 
6132 _("%s%s\n\n"
6133 "Copyright 1991 Digital Equipment Corporation\n"
6134 "Enhancements Copyright 1992-2012 Free Software Foundation\n"
6135 "Enhancements Copyright 2005 Alessandro Scotti\n\n"
6136 "%s is free software and carries NO WARRANTY;"
6137 "see the file COPYING for more information.\n\n"
6138 "Visit XBoard on the web at: http://www.gnu.org/software/xboard/\n"
6139 "Check out the newest features at: http://www.gnu.org/software/xboard/whats_new.html\n\n"
6140 "Report bugs via email at: <bug-xboard@gnu.org>\n\n"
6141   ),
6142             programVersion, zippy, PACKAGE);
6143     ErrorPopUp(_("About XBoard"), buf, FALSE);
6144 }
6145
6146 void
6147 DebugProc ()
6148 {
6149     appData.debugMode = !appData.debugMode;
6150 }
6151
6152 void
6153 NothingProc ()
6154 {
6155     return;
6156 }
6157
6158 void
6159 DisplayMessage (char *message, char *extMessage)
6160 {
6161   /* display a message in the message widget */
6162
6163   char buf[MSG_SIZ];
6164   Arg arg;
6165
6166   if (extMessage)
6167     {
6168       if (*message)
6169         {
6170           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
6171           message = buf;
6172         }
6173       else
6174         {
6175           message = extMessage;
6176         };
6177     };
6178
6179     safeStrCpy(lastMsg, message, MSG_SIZ); // [HGM] make available
6180
6181   /* need to test if messageWidget already exists, since this function
6182      can also be called during the startup, if for example a Xresource
6183      is not set up correctly */
6184   if(messageWidget)
6185     {
6186       XtSetArg(arg, XtNlabel, message);
6187       XtSetValues(messageWidget, &arg, 1);
6188     };
6189
6190   return;
6191 }
6192
6193 void
6194 DisplayTitle (char *text)
6195 {
6196     Arg args[16];
6197     int i;
6198     char title[MSG_SIZ];
6199     char icon[MSG_SIZ];
6200
6201     if (text == NULL) text = "";
6202
6203     if (appData.titleInWindow) {
6204         i = 0;
6205         XtSetArg(args[i], XtNlabel, text);   i++;
6206         XtSetValues(titleWidget, args, i);
6207     }
6208
6209     if (*text != NULLCHAR) {
6210       safeStrCpy(icon, text, sizeof(icon)/sizeof(icon[0]) );
6211       safeStrCpy(title, text, sizeof(title)/sizeof(title[0]) );
6212     } else if (appData.icsActive) {
6213         snprintf(icon, sizeof(icon), "%s", appData.icsHost);
6214         snprintf(title, sizeof(title), "%s: %s", programName, appData.icsHost);
6215     } else if (appData.cmailGameName[0] != NULLCHAR) {
6216         snprintf(icon, sizeof(icon), "%s", "CMail");
6217         snprintf(title,sizeof(title), "%s: %s", programName, "CMail");
6218 #ifdef GOTHIC
6219     // [HGM] license: This stuff should really be done in back-end, but WinBoard already had a pop-up for it
6220     } else if (gameInfo.variant == VariantGothic) {
6221       safeStrCpy(icon,  programName, sizeof(icon)/sizeof(icon[0]) );
6222       safeStrCpy(title, GOTHIC,     sizeof(title)/sizeof(title[0]) );
6223 #endif
6224 #ifdef FALCON
6225     } else if (gameInfo.variant == VariantFalcon) {
6226       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
6227       safeStrCpy(title, FALCON, sizeof(title)/sizeof(title[0]) );
6228 #endif
6229     } else if (appData.noChessProgram) {
6230       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
6231       safeStrCpy(title, programName, sizeof(title)/sizeof(title[0]) );
6232     } else {
6233       safeStrCpy(icon, first.tidy, sizeof(icon)/sizeof(icon[0]) );
6234         snprintf(title,sizeof(title), "%s: %s", programName, first.tidy);
6235     }
6236     i = 0;
6237     XtSetArg(args[i], XtNiconName, (XtArgVal) icon);    i++;
6238     XtSetArg(args[i], XtNtitle, (XtArgVal) title);      i++;
6239     XtSetValues(shellWidget, args, i);
6240     XSync(xDisplay, False);
6241 }
6242
6243
6244 void
6245 DisplayError (String message, int error)
6246 {
6247     char buf[MSG_SIZ];
6248
6249     if (error == 0) {
6250         if (appData.debugMode || appData.matchMode) {
6251             fprintf(stderr, "%s: %s\n", programName, message);
6252         }
6253     } else {
6254         if (appData.debugMode || appData.matchMode) {
6255             fprintf(stderr, "%s: %s: %s\n",
6256                     programName, message, strerror(error));
6257         }
6258         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
6259         message = buf;
6260     }
6261     ErrorPopUp(_("Error"), message, FALSE);
6262 }
6263
6264
6265 void
6266 DisplayMoveError (String message)
6267 {
6268     fromX = fromY = -1;
6269     ClearHighlights();
6270     DrawPosition(FALSE, NULL);
6271     if (appData.debugMode || appData.matchMode) {
6272         fprintf(stderr, "%s: %s\n", programName, message);
6273     }
6274     if (appData.popupMoveErrors) {
6275         ErrorPopUp(_("Error"), message, FALSE);
6276     } else {
6277         DisplayMessage(message, "");
6278     }
6279 }
6280
6281
6282 void
6283 DisplayFatalError (String message, int error, int status)
6284 {
6285     char buf[MSG_SIZ];
6286
6287     errorExitStatus = status;
6288     if (error == 0) {
6289         fprintf(stderr, "%s: %s\n", programName, message);
6290     } else {
6291         fprintf(stderr, "%s: %s: %s\n",
6292                 programName, message, strerror(error));
6293         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
6294         message = buf;
6295     }
6296     if (appData.popupExitMessage && boardWidget && XtIsRealized(boardWidget)) {
6297       ErrorPopUp(status ? _("Fatal Error") : _("Exiting"), message, TRUE);
6298     } else {
6299       ExitEvent(status);
6300     }
6301 }
6302
6303 void
6304 DisplayInformation (String message)
6305 {
6306     ErrorPopDown();
6307     ErrorPopUp(_("Information"), message, TRUE);
6308 }
6309
6310 void
6311 DisplayNote (String message)
6312 {
6313     ErrorPopDown();
6314     ErrorPopUp(_("Note"), message, FALSE);
6315 }
6316
6317 static int
6318 NullXErrorCheck (Display *dpy, XErrorEvent *error_event)
6319 {
6320     return 0;
6321 }
6322
6323 void
6324 DisplayIcsInteractionTitle (String message)
6325 {
6326   if (oldICSInteractionTitle == NULL) {
6327     /* Magic to find the old window title, adapted from vim */
6328     char *wina = getenv("WINDOWID");
6329     if (wina != NULL) {
6330       Window win = (Window) atoi(wina);
6331       Window root, parent, *children;
6332       unsigned int nchildren;
6333       int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
6334       for (;;) {
6335         if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
6336         if (!XQueryTree(xDisplay, win, &root, &parent,
6337                         &children, &nchildren)) break;
6338         if (children) XFree((void *)children);
6339         if (parent == root || parent == 0) break;
6340         win = parent;
6341       }
6342       XSetErrorHandler(oldHandler);
6343     }
6344     if (oldICSInteractionTitle == NULL) {
6345       oldICSInteractionTitle = "xterm";
6346     }
6347   }
6348   printf("\033]0;%s\007", message);
6349   fflush(stdout);
6350 }
6351
6352 char pendingReplyPrefix[MSG_SIZ];
6353 ProcRef pendingReplyPR;
6354
6355 void
6356 AskQuestionProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
6357 {
6358     if (*nprms != 4) {
6359         fprintf(stderr, _("AskQuestionProc needed 4 parameters, got %d\n"),
6360                 *nprms);
6361         return;
6362     }
6363     AskQuestionEvent(prms[0], prms[1], prms[2], prms[3]);
6364 }
6365
6366 void
6367 AskQuestionPopDown ()
6368 {
6369     if (!askQuestionUp) return;
6370     XtPopdown(askQuestionShell);
6371     XtDestroyWidget(askQuestionShell);
6372     askQuestionUp = False;
6373 }
6374
6375 void
6376 AskQuestionReplyAction (Widget w, XEvent *event, String *prms, Cardinal *nprms)
6377 {
6378     char buf[MSG_SIZ];
6379     int err;
6380     String reply;
6381
6382     reply = XawDialogGetValueString(w = XtParent(w));
6383     safeStrCpy(buf, pendingReplyPrefix, sizeof(buf)/sizeof(buf[0]) );
6384     if (*buf) strncat(buf, " ", MSG_SIZ - strlen(buf) - 1);
6385     strncat(buf, reply, MSG_SIZ - strlen(buf) - 1);
6386     strncat(buf, "\n",  MSG_SIZ - strlen(buf) - 1);
6387     OutputToProcess(pendingReplyPR, buf, strlen(buf), &err);
6388     AskQuestionPopDown();
6389
6390     if (err) DisplayFatalError(_("Error writing to chess program"), err, 0);
6391 }
6392
6393 void
6394 AskQuestionCallback (Widget w, XtPointer client_data, XtPointer call_data)
6395 {
6396     String name;
6397     Arg args[16];
6398
6399     XtSetArg(args[0], XtNlabel, &name);
6400     XtGetValues(w, args, 1);
6401
6402     if (strcmp(name, _("cancel")) == 0) {
6403         AskQuestionPopDown();
6404     } else {
6405         AskQuestionReplyAction(w, NULL, NULL, NULL);
6406     }
6407 }
6408
6409 void
6410 AskQuestion (char *title, char *question, char *replyPrefix, ProcRef pr)
6411 {
6412     Arg args[16];
6413     Widget popup, layout, dialog, edit;
6414     Window root, child;
6415     int x, y, i;
6416     int win_x, win_y;
6417     unsigned int mask;
6418
6419     safeStrCpy(pendingReplyPrefix, replyPrefix, sizeof(pendingReplyPrefix)/sizeof(pendingReplyPrefix[0]) );
6420     pendingReplyPR = pr;
6421
6422     i = 0;
6423     XtSetArg(args[i], XtNresizable, True); i++;
6424     XtSetArg(args[i], XtNwidth, DIALOG_SIZE); i++;
6425     askQuestionShell = popup =
6426       XtCreatePopupShell(title, transientShellWidgetClass,
6427                          shellWidget, args, i);
6428
6429     layout =
6430       XtCreateManagedWidget(layoutName, formWidgetClass, popup,
6431                             layoutArgs, XtNumber(layoutArgs));
6432
6433     i = 0;
6434     XtSetArg(args[i], XtNlabel, question); i++;
6435     XtSetArg(args[i], XtNvalue, ""); i++;
6436     XtSetArg(args[i], XtNborderWidth, 0); i++;
6437     dialog = XtCreateManagedWidget("question", dialogWidgetClass,
6438                                    layout, args, i);
6439
6440     XawDialogAddButton(dialog, _("enter"), AskQuestionCallback,
6441                        (XtPointer) dialog);
6442     XawDialogAddButton(dialog, _("cancel"), AskQuestionCallback,
6443                        (XtPointer) dialog);
6444
6445     XtRealizeWidget(popup);
6446     CatchDeleteWindow(popup, "AskQuestionPopDown");
6447
6448     XQueryPointer(xDisplay, xBoardWindow, &root, &child,
6449                   &x, &y, &win_x, &win_y, &mask);
6450
6451     XtSetArg(args[0], XtNx, x - 10);
6452     XtSetArg(args[1], XtNy, y - 30);
6453     XtSetValues(popup, args, 2);
6454
6455     XtPopup(popup, XtGrabExclusive);
6456     askQuestionUp = True;
6457
6458     edit = XtNameToWidget(dialog, "*value");
6459     XtSetKeyboardFocus(popup, edit);
6460 }
6461
6462
6463 void
6464 PlaySound (char *name)
6465 {
6466   if (*name == NULLCHAR) {
6467     return;
6468   } else if (strcmp(name, "$") == 0) {
6469     putc(BELLCHAR, stderr);
6470   } else {
6471     char buf[2048];
6472     char *prefix = "", *sep = "";
6473     if(appData.soundProgram[0] == NULLCHAR) return;
6474     if(!strchr(name, '/')) { prefix = appData.soundDirectory; sep = "/"; }
6475     snprintf(buf, sizeof(buf), "%s '%s%s%s' &", appData.soundProgram, prefix, sep, name);
6476     system(buf);
6477   }
6478 }
6479
6480 void
6481 RingBell ()
6482 {
6483   PlaySound(appData.soundMove);
6484 }
6485
6486 void
6487 PlayIcsWinSound ()
6488 {
6489   PlaySound(appData.soundIcsWin);
6490 }
6491
6492 void
6493 PlayIcsLossSound ()
6494 {
6495   PlaySound(appData.soundIcsLoss);
6496 }
6497
6498 void
6499 PlayIcsDrawSound ()
6500 {
6501   PlaySound(appData.soundIcsDraw);
6502 }
6503
6504 void
6505 PlayIcsUnfinishedSound ()
6506 {
6507   PlaySound(appData.soundIcsUnfinished);
6508 }
6509
6510 void
6511 PlayAlarmSound ()
6512 {
6513   PlaySound(appData.soundIcsAlarm);
6514 }
6515
6516 void
6517 PlayTellSound ()
6518 {
6519   PlaySound(appData.soundTell);
6520 }
6521
6522 void
6523 EchoOn ()
6524 {
6525     system("stty echo");
6526     noEcho = False;
6527 }
6528
6529 void
6530 EchoOff ()
6531 {
6532     system("stty -echo");
6533     noEcho = True;
6534 }
6535
6536 void
6537 RunCommand (char *buf)
6538 {
6539     system(buf);
6540 }
6541
6542 void
6543 Colorize (ColorClass cc, int continuation)
6544 {
6545     char buf[MSG_SIZ];
6546     int count, outCount, error;
6547
6548     if (textColors[(int)cc].bg > 0) {
6549         if (textColors[(int)cc].fg > 0) {
6550           snprintf(buf, MSG_SIZ, "\033[0;%d;%d;%dm", textColors[(int)cc].attr,
6551                    textColors[(int)cc].fg, textColors[(int)cc].bg);
6552         } else {
6553           snprintf(buf, MSG_SIZ, "\033[0;%d;%dm", textColors[(int)cc].attr,
6554                    textColors[(int)cc].bg);
6555         }
6556     } else {
6557         if (textColors[(int)cc].fg > 0) {
6558           snprintf(buf, MSG_SIZ, "\033[0;%d;%dm", textColors[(int)cc].attr,
6559                     textColors[(int)cc].fg);
6560         } else {
6561           snprintf(buf, MSG_SIZ, "\033[0;%dm", textColors[(int)cc].attr);
6562         }
6563     }
6564     count = strlen(buf);
6565     outCount = OutputToProcess(NoProc, buf, count, &error);
6566     if (outCount < count) {
6567         DisplayFatalError(_("Error writing to display"), error, 1);
6568     }
6569
6570     if (continuation) return;
6571     switch (cc) {
6572     case ColorShout:
6573       PlaySound(appData.soundShout);
6574       break;
6575     case ColorSShout:
6576       PlaySound(appData.soundSShout);
6577       break;
6578     case ColorChannel1:
6579       PlaySound(appData.soundChannel1);
6580       break;
6581     case ColorChannel:
6582       PlaySound(appData.soundChannel);
6583       break;
6584     case ColorKibitz:
6585       PlaySound(appData.soundKibitz);
6586       break;
6587     case ColorTell:
6588       PlaySound(appData.soundTell);
6589       break;
6590     case ColorChallenge:
6591       PlaySound(appData.soundChallenge);
6592       break;
6593     case ColorRequest:
6594       PlaySound(appData.soundRequest);
6595       break;
6596     case ColorSeek:
6597       PlaySound(appData.soundSeek);
6598       break;
6599     case ColorNormal:
6600     case ColorNone:
6601     default:
6602       break;
6603     }
6604 }
6605
6606 char *
6607 UserName ()
6608 {
6609     return getpwuid(getuid())->pw_name;
6610 }
6611
6612 static char *
6613 ExpandPathName (char *path)
6614 {
6615     static char static_buf[4*MSG_SIZ];
6616     char *d, *s, buf[4*MSG_SIZ];
6617     struct passwd *pwd;
6618
6619     s = path;
6620     d = static_buf;
6621
6622     while (*s && isspace(*s))
6623       ++s;
6624
6625     if (!*s) {
6626         *d = 0;
6627         return static_buf;
6628     }
6629
6630     if (*s == '~') {
6631         if (*(s+1) == '/') {
6632           safeStrCpy(d, getpwuid(getuid())->pw_dir, 4*MSG_SIZ );
6633           strcat(d, s+1);
6634         }
6635         else {
6636           safeStrCpy(buf, s+1, sizeof(buf)/sizeof(buf[0]) );
6637           { char *p; if(p = strchr(buf, '/')) *p = 0; }
6638           pwd = getpwnam(buf);
6639           if (!pwd)
6640             {
6641               fprintf(stderr, _("ERROR: Unknown user %s (in path %s)\n"),
6642                       buf, path);
6643               return NULL;
6644             }
6645           safeStrCpy(d, pwd->pw_dir, 4*MSG_SIZ );
6646           strcat(d, strchr(s+1, '/'));
6647         }
6648     }
6649     else
6650       safeStrCpy(d, s, 4*MSG_SIZ );
6651
6652     return static_buf;
6653 }
6654
6655 char *
6656 HostName ()
6657 {
6658     static char host_name[MSG_SIZ];
6659
6660 #if HAVE_GETHOSTNAME
6661     gethostname(host_name, MSG_SIZ);
6662     return host_name;
6663 #else  /* not HAVE_GETHOSTNAME */
6664 # if HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H
6665     sysinfo(SI_HOSTNAME, host_name, MSG_SIZ);
6666     return host_name;
6667 # else /* not (HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H) */
6668     return "localhost";
6669 # endif/* not (HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H) */
6670 #endif /* not HAVE_GETHOSTNAME */
6671 }
6672
6673 XtIntervalId delayedEventTimerXID = 0;
6674 DelayedEventCallback delayedEventCallback = 0;
6675
6676 void
6677 FireDelayedEvent ()
6678 {
6679     delayedEventTimerXID = 0;
6680     delayedEventCallback();
6681 }
6682
6683 void
6684 ScheduleDelayedEvent (DelayedEventCallback cb, long millisec)
6685 {
6686     if(delayedEventTimerXID && delayedEventCallback == cb)
6687         // [HGM] alive: replace, rather than add or flush identical event
6688         XtRemoveTimeOut(delayedEventTimerXID);
6689     delayedEventCallback = cb;
6690     delayedEventTimerXID =
6691       XtAppAddTimeOut(appContext, millisec,
6692                       (XtTimerCallbackProc) FireDelayedEvent, (XtPointer) 0);
6693 }
6694
6695 DelayedEventCallback
6696 GetDelayedEvent ()
6697 {
6698   if (delayedEventTimerXID) {
6699     return delayedEventCallback;
6700   } else {
6701     return NULL;
6702   }
6703 }
6704
6705 void
6706 CancelDelayedEvent ()
6707 {
6708   if (delayedEventTimerXID) {
6709     XtRemoveTimeOut(delayedEventTimerXID);
6710     delayedEventTimerXID = 0;
6711   }
6712 }
6713
6714 XtIntervalId loadGameTimerXID = 0;
6715
6716 int
6717 LoadGameTimerRunning ()
6718 {
6719     return loadGameTimerXID != 0;
6720 }
6721
6722 int
6723 StopLoadGameTimer ()
6724 {
6725     if (loadGameTimerXID != 0) {
6726         XtRemoveTimeOut(loadGameTimerXID);
6727         loadGameTimerXID = 0;
6728         return TRUE;
6729     } else {
6730         return FALSE;
6731     }
6732 }
6733
6734 void
6735 LoadGameTimerCallback (XtPointer arg, XtIntervalId *id)
6736 {
6737     loadGameTimerXID = 0;
6738     AutoPlayGameLoop();
6739 }
6740
6741 void
6742 StartLoadGameTimer (long millisec)
6743 {
6744     loadGameTimerXID =
6745       XtAppAddTimeOut(appContext, millisec,
6746                       (XtTimerCallbackProc) LoadGameTimerCallback,
6747                       (XtPointer) 0);
6748 }
6749
6750 XtIntervalId analysisClockXID = 0;
6751
6752 void
6753 AnalysisClockCallback (XtPointer arg, XtIntervalId *id)
6754 {
6755     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
6756          || appData.icsEngineAnalyze) { // [DM]
6757         AnalysisPeriodicEvent(0);
6758         StartAnalysisClock();
6759     }
6760 }
6761
6762 void
6763 StartAnalysisClock ()
6764 {
6765     analysisClockXID =
6766       XtAppAddTimeOut(appContext, 2000,
6767                       (XtTimerCallbackProc) AnalysisClockCallback,
6768                       (XtPointer) 0);
6769 }
6770
6771 XtIntervalId clockTimerXID = 0;
6772
6773 int
6774 ClockTimerRunning ()
6775 {
6776     return clockTimerXID != 0;
6777 }
6778
6779 int
6780 StopClockTimer ()
6781 {
6782     if (clockTimerXID != 0) {
6783         XtRemoveTimeOut(clockTimerXID);
6784         clockTimerXID = 0;
6785         return TRUE;
6786     } else {
6787         return FALSE;
6788     }
6789 }
6790
6791 void
6792 ClockTimerCallback (XtPointer arg, XtIntervalId *id)
6793 {
6794     clockTimerXID = 0;
6795     DecrementClocks();
6796 }
6797
6798 void
6799 StartClockTimer (long millisec)
6800 {
6801     clockTimerXID =
6802       XtAppAddTimeOut(appContext, millisec,
6803                       (XtTimerCallbackProc) ClockTimerCallback,
6804                       (XtPointer) 0);
6805 }
6806
6807 void
6808 DisplayTimerLabel (Widget w, char *color, long timer, int highlight)
6809 {
6810     char buf[MSG_SIZ];
6811     Arg args[16];
6812
6813     /* check for low time warning */
6814     Pixel foregroundOrWarningColor = timerForegroundPixel;
6815
6816     if (timer > 0 &&
6817         appData.lowTimeWarning &&
6818         (timer / 1000) < appData.icsAlarmTime)
6819       foregroundOrWarningColor = lowTimeWarningColor;
6820
6821     if (appData.clockMode) {
6822       snprintf(buf, MSG_SIZ, "%s: %s", color, TimeString(timer));
6823       XtSetArg(args[0], XtNlabel, buf);
6824     } else {
6825       snprintf(buf, MSG_SIZ, "%s  ", color);
6826       XtSetArg(args[0], XtNlabel, buf);
6827     }
6828
6829     if (highlight) {
6830
6831         XtSetArg(args[1], XtNbackground, foregroundOrWarningColor);
6832         XtSetArg(args[2], XtNforeground, timerBackgroundPixel);
6833     } else {
6834         XtSetArg(args[1], XtNbackground, timerBackgroundPixel);
6835         XtSetArg(args[2], XtNforeground, foregroundOrWarningColor);
6836     }
6837
6838     XtSetValues(w, args, 3);
6839 }
6840
6841 void
6842 DisplayWhiteClock (long timeRemaining, int highlight)
6843 {
6844     Arg args[16];
6845
6846     if(appData.noGUI) return;
6847     DisplayTimerLabel(whiteTimerWidget, _("White"), timeRemaining, highlight);
6848     if (highlight && iconPixmap == bIconPixmap) {
6849         iconPixmap = wIconPixmap;
6850         XtSetArg(args[0], XtNiconPixmap, iconPixmap);
6851         XtSetValues(shellWidget, args, 1);
6852     }
6853 }
6854
6855 void
6856 DisplayBlackClock (long timeRemaining, int highlight)
6857 {
6858     Arg args[16];
6859
6860     if(appData.noGUI) return;
6861     DisplayTimerLabel(blackTimerWidget, _("Black"), timeRemaining, highlight);
6862     if (highlight && iconPixmap == wIconPixmap) {
6863         iconPixmap = bIconPixmap;
6864         XtSetArg(args[0], XtNiconPixmap, iconPixmap);
6865         XtSetValues(shellWidget, args, 1);
6866     }
6867 }
6868
6869 #define CPNone 0
6870 #define CPReal 1
6871 #define CPComm 2
6872 #define CPSock 3
6873 #define CPLoop 4
6874 typedef int CPKind;
6875
6876 typedef struct {
6877     CPKind kind;
6878     int pid;
6879     int fdTo, fdFrom;
6880 } ChildProc;
6881
6882
6883 int
6884 StartChildProcess (char *cmdLine, char *dir, ProcRef *pr)
6885 {
6886     char *argv[64], *p;
6887     int i, pid;
6888     int to_prog[2], from_prog[2];
6889     ChildProc *cp;
6890     char buf[MSG_SIZ];
6891
6892     if (appData.debugMode) {
6893         fprintf(debugFP, "StartChildProcess (dir=\"%s\") %s\n",dir, cmdLine);
6894     }
6895
6896     /* We do NOT feed the cmdLine to the shell; we just
6897        parse it into blank-separated arguments in the
6898        most simple-minded way possible.
6899        */
6900     i = 0;
6901     safeStrCpy(buf, cmdLine, sizeof(buf)/sizeof(buf[0]) );
6902     p = buf;
6903     for (;;) {
6904         while(*p == ' ') p++;
6905         argv[i++] = p;
6906         if(*p == '"' || *p == '\'')
6907              p = strchr(++argv[i-1], *p);
6908         else p = strchr(p, ' ');
6909         if (p == NULL) break;
6910         *p++ = NULLCHAR;
6911     }
6912     argv[i] = NULL;
6913
6914     SetUpChildIO(to_prog, from_prog);
6915
6916     if ((pid = fork()) == 0) {
6917         /* Child process */
6918         // [HGM] PSWBTM: made order resistant against case where fd of created pipe was 0 or 1
6919         close(to_prog[1]);     // first close the unused pipe ends
6920         close(from_prog[0]);
6921         dup2(to_prog[0], 0);   // to_prog was created first, nd is the only one to use 0 or 1
6922         dup2(from_prog[1], 1);
6923         if(to_prog[0] >= 2) close(to_prog[0]); // if 0 or 1, the dup2 already cosed the original
6924         close(from_prog[1]);                   // and closing again loses one of the pipes!
6925         if(fileno(stderr) >= 2) // better safe than sorry...
6926                 dup2(1, fileno(stderr)); /* force stderr to the pipe */
6927
6928         if (dir[0] != NULLCHAR && chdir(dir) != 0) {
6929             perror(dir);
6930             exit(1);
6931         }
6932
6933         nice(appData.niceEngines); // [HGM] nice: adjust priority of engine proc
6934
6935         execvp(argv[0], argv);
6936
6937         /* If we get here, exec failed */
6938         perror(argv[0]);
6939         exit(1);
6940     }
6941
6942     /* Parent process */
6943     close(to_prog[0]);
6944     close(from_prog[1]);
6945
6946     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
6947     cp->kind = CPReal;
6948     cp->pid = pid;
6949     cp->fdFrom = from_prog[0];
6950     cp->fdTo = to_prog[1];
6951     *pr = (ProcRef) cp;
6952     return 0;
6953 }
6954
6955 // [HGM] kill: implement the 'hard killing' of AS's Winboard_x
6956 static RETSIGTYPE
6957 AlarmCallBack (int n)
6958 {
6959     return;
6960 }
6961
6962 void
6963 DestroyChildProcess (ProcRef pr, int signalType)
6964 {
6965     ChildProc *cp = (ChildProc *) pr;
6966
6967     if (cp->kind != CPReal) return;
6968     cp->kind = CPNone;
6969     if (signalType == 10) { // [HGM] kill: if it does not terminate in 3 sec, kill
6970         signal(SIGALRM, AlarmCallBack);
6971         alarm(3);
6972         if(wait((int *) 0) == -1) { // process does not terminate on its own accord
6973             kill(cp->pid, SIGKILL); // kill it forcefully
6974             wait((int *) 0);        // and wait again
6975         }
6976     } else {
6977         if (signalType) {
6978             kill(cp->pid, signalType == 9 ? SIGKILL : SIGTERM); // [HGM] kill: use hard kill if so requested
6979         }
6980         /* Process is exiting either because of the kill or because of
6981            a quit command sent by the backend; either way, wait for it to die.
6982         */
6983         wait((int *) 0);
6984     }
6985     close(cp->fdFrom);
6986     close(cp->fdTo);
6987 }
6988
6989 void
6990 InterruptChildProcess (ProcRef pr)
6991 {
6992     ChildProc *cp = (ChildProc *) pr;
6993
6994     if (cp->kind != CPReal) return;
6995     (void) kill(cp->pid, SIGINT); /* stop it thinking */
6996 }
6997
6998 int
6999 OpenTelnet (char *host, char *port, ProcRef *pr)
7000 {
7001     char cmdLine[MSG_SIZ];
7002
7003     if (port[0] == NULLCHAR) {
7004       snprintf(cmdLine, sizeof(cmdLine), "%s %s", appData.telnetProgram, host);
7005     } else {
7006       snprintf(cmdLine, sizeof(cmdLine), "%s %s %s", appData.telnetProgram, host, port);
7007     }
7008     return StartChildProcess(cmdLine, "", pr);
7009 }
7010
7011 int
7012 OpenTCP (char *host, char *port, ProcRef *pr)
7013 {
7014 #if OMIT_SOCKETS
7015     DisplayFatalError(_("Socket support is not configured in"), 0, 2);
7016 #else  /* !OMIT_SOCKETS */
7017     struct addrinfo hints;
7018     struct addrinfo *ais, *ai;
7019     int error;
7020     int s=0;
7021     ChildProc *cp;
7022
7023     memset(&hints, 0, sizeof(hints));
7024     hints.ai_family = AF_UNSPEC;
7025     hints.ai_socktype = SOCK_STREAM;
7026
7027     error = getaddrinfo(host, port, &hints, &ais);
7028     if (error != 0) {
7029       /* a getaddrinfo error is not an errno, so can't return it */
7030       fprintf(debugFP, "getaddrinfo(%s, %s): %s\n",
7031               host, port, gai_strerror(error));
7032       return ENOENT;
7033     }
7034      
7035     for (ai = ais; ai != NULL; ai = ai->ai_next) {
7036       if ((s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0) {
7037         error = errno;
7038         continue;
7039       }
7040       if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) {
7041         error = errno;
7042         continue;
7043       }
7044       error = 0;
7045       break;
7046     }
7047     freeaddrinfo(ais);
7048
7049     if (error != 0) {
7050       return error;
7051     }
7052
7053     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
7054     cp->kind = CPSock;
7055     cp->pid = 0;
7056     cp->fdFrom = s;
7057     cp->fdTo = s;
7058     *pr = (ProcRef) cp;
7059 #endif /* !OMIT_SOCKETS */
7060
7061     return 0;
7062 }
7063
7064 int
7065 OpenCommPort (char *name, ProcRef *pr)
7066 {
7067     int fd;
7068     ChildProc *cp;
7069
7070     fd = open(name, 2, 0);
7071     if (fd < 0) return errno;
7072
7073     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
7074     cp->kind = CPComm;
7075     cp->pid = 0;
7076     cp->fdFrom = fd;
7077     cp->fdTo = fd;
7078     *pr = (ProcRef) cp;
7079
7080     return 0;
7081 }
7082
7083 int
7084 OpenLoopback (ProcRef *pr)
7085 {
7086     ChildProc *cp;
7087     int to[2], from[2];
7088
7089     SetUpChildIO(to, from);
7090
7091     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
7092     cp->kind = CPLoop;
7093     cp->pid = 0;
7094     cp->fdFrom = to[0];         /* note not from[0]; we are doing a loopback */
7095     cp->fdTo = to[1];
7096     *pr = (ProcRef) cp;
7097
7098     return 0;
7099 }
7100
7101 int
7102 OpenRcmd (char *host, char *user, char *cmd, ProcRef *pr)
7103 {
7104     DisplayFatalError(_("internal rcmd not implemented for Unix"), 0, 1);
7105     return -1;
7106 }
7107
7108 #define INPUT_SOURCE_BUF_SIZE 8192
7109
7110 typedef struct {
7111     CPKind kind;
7112     int fd;
7113     int lineByLine;
7114     char *unused;
7115     InputCallback func;
7116     XtInputId xid;
7117     char buf[INPUT_SOURCE_BUF_SIZE];
7118     VOIDSTAR closure;
7119 } InputSource;
7120
7121 void
7122 DoInputCallback (caddr_t closure, int *source, XtInputId *xid)
7123 {
7124     InputSource *is = (InputSource *) closure;
7125     int count;
7126     int error;
7127     char *p, *q;
7128
7129     if (is->lineByLine) {
7130         count = read(is->fd, is->unused,
7131                      INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
7132         if (count <= 0) {
7133             (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
7134             return;
7135         }
7136         is->unused += count;
7137         p = is->buf;
7138         while (p < is->unused) {
7139             q = memchr(p, '\n', is->unused - p);
7140             if (q == NULL) break;
7141             q++;
7142             (is->func)(is, is->closure, p, q - p, 0);
7143             p = q;
7144         }
7145         q = is->buf;
7146         while (p < is->unused) {
7147             *q++ = *p++;
7148         }
7149         is->unused = q;
7150     } else {
7151         count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
7152         if (count == -1)
7153           error = errno;
7154         else
7155           error = 0;
7156         (is->func)(is, is->closure, is->buf, count, error);
7157     }
7158 }
7159
7160 InputSourceRef
7161 AddInputSource (ProcRef pr, int lineByLine, InputCallback func, VOIDSTAR closure)
7162 {
7163     InputSource *is;
7164     ChildProc *cp = (ChildProc *) pr;
7165
7166     is = (InputSource *) calloc(1, sizeof(InputSource));
7167     is->lineByLine = lineByLine;
7168     is->func = func;
7169     if (pr == NoProc) {
7170         is->kind = CPReal;
7171         is->fd = fileno(stdin);
7172     } else {
7173         is->kind = cp->kind;
7174         is->fd = cp->fdFrom;
7175     }
7176     if (lineByLine) {
7177         is->unused = is->buf;
7178     }
7179
7180     is->xid = XtAppAddInput(appContext, is->fd,
7181                             (XtPointer) (XtInputReadMask),
7182                             (XtInputCallbackProc) DoInputCallback,
7183                             (XtPointer) is);
7184     is->closure = closure;
7185     return (InputSourceRef) is;
7186 }
7187
7188 void
7189 RemoveInputSource (InputSourceRef isr)
7190 {
7191     InputSource *is = (InputSource *) isr;
7192
7193     if (is->xid == 0) return;
7194     XtRemoveInput(is->xid);
7195     is->xid = 0;
7196 }
7197
7198 int
7199 OutputToProcess (ProcRef pr, char *message, int count, int *outError)
7200 {
7201     static int line = 0;
7202     ChildProc *cp = (ChildProc *) pr;
7203     int outCount;
7204
7205     if (pr == NoProc)
7206     {
7207         if (appData.noJoin || !appData.useInternalWrap)
7208             outCount = fwrite(message, 1, count, stdout);
7209         else
7210         {
7211             int width = get_term_width();
7212             int len = wrap(NULL, message, count, width, &line);
7213             char *msg = malloc(len);
7214             int dbgchk;
7215
7216             if (!msg)
7217                 outCount = fwrite(message, 1, count, stdout);
7218             else
7219             {
7220                 dbgchk = wrap(msg, message, count, width, &line);
7221                 if (dbgchk != len && appData.debugMode)
7222                     fprintf(debugFP, "wrap(): dbgchk(%d) != len(%d)\n", dbgchk, len);
7223                 outCount = fwrite(msg, 1, dbgchk, stdout);
7224                 free(msg);
7225             }
7226         }
7227     }
7228     else
7229       outCount = write(cp->fdTo, message, count);
7230
7231     if (outCount == -1)
7232       *outError = errno;
7233     else
7234       *outError = 0;
7235
7236     return outCount;
7237 }
7238
7239 /* Output message to process, with "ms" milliseconds of delay
7240    between each character. This is needed when sending the logon
7241    script to ICC, which for some reason doesn't like the
7242    instantaneous send. */
7243 int
7244 OutputToProcessDelayed (ProcRef pr, char *message, int count, int *outError, long msdelay)
7245 {
7246     ChildProc *cp = (ChildProc *) pr;
7247     int outCount = 0;
7248     int r;
7249
7250     while (count--) {
7251         r = write(cp->fdTo, message++, 1);
7252         if (r == -1) {
7253             *outError = errno;
7254             return outCount;
7255         }
7256         ++outCount;
7257         if (msdelay >= 0)
7258           TimeDelay(msdelay);
7259     }
7260
7261     return outCount;
7262 }
7263
7264 /****   Animation code by Hugh Fisher, DCS, ANU.
7265
7266         Known problem: if a window overlapping the board is
7267         moved away while a piece is being animated underneath,
7268         the newly exposed area won't be updated properly.
7269         I can live with this.
7270
7271         Known problem: if you look carefully at the animation
7272         of pieces in mono mode, they are being drawn as solid
7273         shapes without interior detail while moving. Fixing
7274         this would be a major complication for minimal return.
7275 ****/
7276
7277 /*      Masks for XPM pieces. Black and white pieces can have
7278         different shapes, but in the interest of retaining my
7279         sanity pieces must have the same outline on both light
7280         and dark squares, and all pieces must use the same
7281         background square colors/images.                */
7282
7283 static int xpmDone = 0;
7284
7285 static void
7286 CreateAnimMasks (int pieceDepth)
7287 {
7288   ChessSquare   piece;
7289   Pixmap        buf;
7290   GC            bufGC, maskGC;
7291   int           kind, n;
7292   unsigned long plane;
7293   XGCValues     values;
7294
7295   /* Need a bitmap just to get a GC with right depth */
7296   buf = XCreatePixmap(xDisplay, xBoardWindow,
7297                         8, 8, 1);
7298   values.foreground = 1;
7299   values.background = 0;
7300   /* Don't use XtGetGC, not read only */
7301   maskGC = XCreateGC(xDisplay, buf,
7302                     GCForeground | GCBackground, &values);
7303   XFreePixmap(xDisplay, buf);
7304
7305   buf = XCreatePixmap(xDisplay, xBoardWindow,
7306                       squareSize, squareSize, pieceDepth);
7307   values.foreground = XBlackPixel(xDisplay, xScreen);
7308   values.background = XWhitePixel(xDisplay, xScreen);
7309   bufGC = XCreateGC(xDisplay, buf,
7310                     GCForeground | GCBackground, &values);
7311
7312   for (piece = WhitePawn; piece <= BlackKing; piece++) {
7313     /* Begin with empty mask */
7314     if(!xpmDone) // [HGM] pieces: keep using existing
7315     xpmMask[piece] = XCreatePixmap(xDisplay, xBoardWindow,
7316                                  squareSize, squareSize, 1);
7317     XSetFunction(xDisplay, maskGC, GXclear);
7318     XFillRectangle(xDisplay, xpmMask[piece], maskGC,
7319                    0, 0, squareSize, squareSize);
7320
7321     /* Take a copy of the piece */
7322     if (White(piece))
7323       kind = 0;
7324     else
7325       kind = 2;
7326     XSetFunction(xDisplay, bufGC, GXcopy);
7327     XCopyArea(xDisplay, xpmPieceBitmap[kind][((int)piece) % (int)BlackPawn],
7328               buf, bufGC,
7329               0, 0, squareSize, squareSize, 0, 0);
7330
7331     /* XOR the background (light) over the piece */
7332     XSetFunction(xDisplay, bufGC, GXxor);
7333     if (useImageSqs)
7334       XCopyArea(xDisplay, xpmLightSquare, buf, bufGC,
7335                 0, 0, squareSize, squareSize, 0, 0);
7336     else {
7337       XSetForeground(xDisplay, bufGC, lightSquareColor);
7338       XFillRectangle(xDisplay, buf, bufGC, 0, 0, squareSize, squareSize);
7339     }
7340
7341     /* We now have an inverted piece image with the background
7342        erased. Construct mask by just selecting all the non-zero
7343        pixels - no need to reconstruct the original image.      */
7344     XSetFunction(xDisplay, maskGC, GXor);
7345     plane = 1;
7346     /* Might be quicker to download an XImage and create bitmap
7347        data from it rather than this N copies per piece, but it
7348        only takes a fraction of a second and there is a much
7349        longer delay for loading the pieces.             */
7350     for (n = 0; n < pieceDepth; n ++) {
7351       XCopyPlane(xDisplay, buf, xpmMask[piece], maskGC,
7352                  0, 0, squareSize, squareSize,
7353                  0, 0, plane);
7354       plane = plane << 1;
7355     }
7356   }
7357   /* Clean up */
7358   XFreePixmap(xDisplay, buf);
7359   XFreeGC(xDisplay, bufGC);
7360   XFreeGC(xDisplay, maskGC);
7361 }
7362
7363 static void
7364 InitAnimState (AnimState *anim, XWindowAttributes *info)
7365 {
7366   XtGCMask  mask;
7367   XGCValues values;
7368
7369   /* Each buffer is square size, same depth as window */
7370   anim->saveBuf = XCreatePixmap(xDisplay, xBoardWindow,
7371                         squareSize, squareSize, info->depth);
7372   anim->newBuf = XCreatePixmap(xDisplay, xBoardWindow,
7373                         squareSize, squareSize, info->depth);
7374
7375   /* Create a plain GC for blitting */
7376   mask = GCForeground | GCBackground | GCFunction |
7377          GCPlaneMask | GCGraphicsExposures;
7378   values.foreground = XBlackPixel(xDisplay, xScreen);
7379   values.background = XWhitePixel(xDisplay, xScreen);
7380   values.function   = GXcopy;
7381   values.plane_mask = AllPlanes;
7382   values.graphics_exposures = False;
7383   anim->blitGC = XCreateGC(xDisplay, xBoardWindow, mask, &values);
7384
7385   /* Piece will be copied from an existing context at
7386      the start of each new animation/drag. */
7387   anim->pieceGC = XCreateGC(xDisplay, xBoardWindow, 0, &values);
7388
7389   /* Outline will be a read-only copy of an existing */
7390   anim->outlineGC = None;
7391 }
7392
7393 static void
7394 CreateAnimVars ()
7395 {
7396   XWindowAttributes info;
7397
7398   if (xpmDone && gameInfo.variant == oldVariant) return;
7399   if(xpmDone) oldVariant = gameInfo.variant; // first time pieces might not be created yet
7400   XGetWindowAttributes(xDisplay, xBoardWindow, &info);
7401
7402   InitAnimState(&game, &info);
7403   InitAnimState(&player, &info);
7404
7405   /* For XPM pieces, we need bitmaps to use as masks. */
7406   if (useImages)
7407     CreateAnimMasks(info.depth), xpmDone = 1;
7408 }
7409
7410 #ifndef HAVE_USLEEP
7411
7412 static Boolean frameWaiting;
7413
7414 static RETSIGTYPE
7415 FrameAlarm (int sig)
7416 {
7417   frameWaiting = False;
7418   /* In case System-V style signals.  Needed?? */
7419   signal(SIGALRM, FrameAlarm);
7420 }
7421
7422 static void
7423 FrameDelay (int time)
7424 {
7425   struct itimerval delay;
7426
7427   XSync(xDisplay, False);
7428
7429   if (time > 0) {
7430     frameWaiting = True;
7431     signal(SIGALRM, FrameAlarm);
7432     delay.it_interval.tv_sec =
7433       delay.it_value.tv_sec = time / 1000;
7434     delay.it_interval.tv_usec =
7435       delay.it_value.tv_usec = (time % 1000) * 1000;
7436     setitimer(ITIMER_REAL, &delay, NULL);
7437     while (frameWaiting) pause();
7438     delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
7439     delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
7440     setitimer(ITIMER_REAL, &delay, NULL);
7441   }
7442 }
7443
7444 #else
7445
7446 static void
7447 FrameDelay (int time)
7448 {
7449   XSync(xDisplay, False);
7450   if (time > 0)
7451     usleep(time * 1000);
7452 }
7453
7454 #endif
7455
7456 void
7457 DoSleep (int n)
7458 {
7459     FrameDelay(n);
7460 }
7461
7462 /*      Convert board position to corner of screen rect and color       */
7463
7464 static void
7465 ScreenSquare (int column, int row, XPoint *pt, int *color)
7466 {
7467   if (flipView) {
7468     pt->x = lineGap + ((BOARD_WIDTH-1)-column) * (squareSize + lineGap);
7469     pt->y = lineGap + row * (squareSize + lineGap);
7470   } else {
7471     pt->x = lineGap + column * (squareSize + lineGap);
7472     pt->y = lineGap + ((BOARD_HEIGHT-1)-row) * (squareSize + lineGap);
7473   }
7474   *color = SquareColor(row, column);
7475 }
7476
7477 /*      Convert window coords to square                 */
7478
7479 static void
7480 BoardSquare (int x, int y, int *column, int *row)
7481 {
7482   *column = EventToSquare(x, BOARD_WIDTH);
7483   if (flipView && *column >= 0)
7484     *column = BOARD_WIDTH - 1 - *column;
7485   *row = EventToSquare(y, BOARD_HEIGHT);
7486   if (!flipView && *row >= 0)
7487     *row = BOARD_HEIGHT - 1 - *row;
7488 }
7489
7490 /*   Utilities  */
7491
7492 #undef Max  /* just in case */
7493 #undef Min
7494 #define Max(a, b) ((a) > (b) ? (a) : (b))
7495 #define Min(a, b) ((a) < (b) ? (a) : (b))
7496
7497 static void
7498 SetRect (XRectangle *rect, int x, int y, int width, int height)
7499 {
7500   rect->x = x;
7501   rect->y = y;
7502   rect->width  = width;
7503   rect->height = height;
7504 }
7505
7506 /*      Test if two frames overlap. If they do, return
7507         intersection rect within old and location of
7508         that rect within new. */
7509
7510 static Boolean
7511 Intersect ( XPoint *old, XPoint *new, int size, XRectangle *area, XPoint *pt)
7512 {
7513   if (old->x > new->x + size || new->x > old->x + size ||
7514       old->y > new->y + size || new->y > old->y + size) {
7515     return False;
7516   } else {
7517     SetRect(area, Max(new->x - old->x, 0), Max(new->y - old->y, 0),
7518             size - abs(old->x - new->x), size - abs(old->y - new->y));
7519     pt->x = Max(old->x - new->x, 0);
7520     pt->y = Max(old->y - new->y, 0);
7521     return True;
7522   }
7523 }
7524
7525 /*      For two overlapping frames, return the rect(s)
7526         in the old that do not intersect with the new.   */
7527
7528 static void
7529 CalcUpdateRects (XPoint *old, XPoint *new, int size, XRectangle update[], int *nUpdates)
7530 {
7531   int        count;
7532
7533   /* If old = new (shouldn't happen) then nothing to draw */
7534   if (old->x == new->x && old->y == new->y) {
7535     *nUpdates = 0;
7536     return;
7537   }
7538   /* Work out what bits overlap. Since we know the rects
7539      are the same size we don't need a full intersect calc. */
7540   count = 0;
7541   /* Top or bottom edge? */
7542   if (new->y > old->y) {
7543     SetRect(&(update[count]), old->x, old->y, size, new->y - old->y);
7544     count ++;
7545   } else if (old->y > new->y) {
7546     SetRect(&(update[count]), old->x, old->y + size - (old->y - new->y),
7547                               size, old->y - new->y);
7548     count ++;
7549   }
7550   /* Left or right edge - don't overlap any update calculated above. */
7551   if (new->x > old->x) {
7552     SetRect(&(update[count]), old->x, Max(new->y, old->y),
7553                               new->x - old->x, size - abs(new->y - old->y));
7554     count ++;
7555   } else if (old->x > new->x) {
7556     SetRect(&(update[count]), new->x + size, Max(new->y, old->y),
7557                               old->x - new->x, size - abs(new->y - old->y));
7558     count ++;
7559   }
7560   /* Done */
7561   *nUpdates = count;
7562 }
7563
7564 /*      Generate a series of frame coords from start->mid->finish.
7565         The movement rate doubles until the half way point is
7566         reached, then halves back down to the final destination,
7567         which gives a nice slow in/out effect. The algorithmn
7568         may seem to generate too many intermediates for short
7569         moves, but remember that the purpose is to attract the
7570         viewers attention to the piece about to be moved and
7571         then to where it ends up. Too few frames would be less
7572         noticeable.                                             */
7573
7574 static void
7575 Tween (XPoint *start, XPoint *mid, XPoint *finish, int factor, XPoint frames[], int *nFrames)
7576 {
7577   int fraction, n, count;
7578
7579   count = 0;
7580
7581   /* Slow in, stepping 1/16th, then 1/8th, ... */
7582   fraction = 1;
7583   for (n = 0; n < factor; n++)
7584     fraction *= 2;
7585   for (n = 0; n < factor; n++) {
7586     frames[count].x = start->x + (mid->x - start->x) / fraction;
7587     frames[count].y = start->y + (mid->y - start->y) / fraction;
7588     count ++;
7589     fraction = fraction / 2;
7590   }
7591
7592   /* Midpoint */
7593   frames[count] = *mid;
7594   count ++;
7595
7596   /* Slow out, stepping 1/2, then 1/4, ... */
7597   fraction = 2;
7598   for (n = 0; n < factor; n++) {
7599     frames[count].x = finish->x - (finish->x - mid->x) / fraction;
7600     frames[count].y = finish->y - (finish->y - mid->y) / fraction;
7601     count ++;
7602     fraction = fraction * 2;
7603   }
7604   *nFrames = count;
7605 }
7606
7607 /*      Draw a piece on the screen without disturbing what's there      */
7608
7609 static void
7610 SelectGCMask (ChessSquare piece, GC *clip, GC *outline, Pixmap *mask)
7611 {
7612   GC source;
7613
7614   /* Bitmap for piece being moved. */
7615   if (appData.monoMode) {
7616       *mask = *pieceToSolid(piece);
7617   } else if (useImages) {
7618 #if HAVE_LIBXPM
7619       *mask = xpmMask[piece];
7620 #else
7621       *mask = ximMaskPm[piece];
7622 #endif
7623   } else {
7624       *mask = *pieceToSolid(piece);
7625   }
7626
7627   /* GC for piece being moved. Square color doesn't matter, but
7628      since it gets modified we make a copy of the original. */
7629   if (White(piece)) {
7630     if (appData.monoMode)
7631       source = bwPieceGC;
7632     else
7633       source = wlPieceGC;
7634   } else {
7635     if (appData.monoMode)
7636       source = wbPieceGC;
7637     else
7638       source = blPieceGC;
7639   }
7640   XCopyGC(xDisplay, source, 0xFFFFFFFF, *clip);
7641
7642   /* Outline only used in mono mode and is not modified */
7643   if (White(piece))
7644     *outline = bwPieceGC;
7645   else
7646     *outline = wbPieceGC;
7647 }
7648
7649 static void
7650 OverlayPiece (ChessSquare piece, GC clip, GC outline,  Drawable dest)
7651 {
7652   int   kind;
7653
7654   if (!useImages) {
7655     /* Draw solid rectangle which will be clipped to shape of piece */
7656     XFillRectangle(xDisplay, dest, clip,
7657                    0, 0, squareSize, squareSize);
7658     if (appData.monoMode)
7659       /* Also draw outline in contrasting color for black
7660          on black / white on white cases                */
7661       XCopyPlane(xDisplay, *pieceToOutline(piece), dest, outline,
7662                  0, 0, squareSize, squareSize, 0, 0, 1);
7663   } else {
7664     /* Copy the piece */
7665     if (White(piece))
7666       kind = 0;
7667     else
7668       kind = 2;
7669     if(appData.upsideDown && flipView) kind ^= 2;
7670     XCopyArea(xDisplay, xpmPieceBitmap[kind][piece],
7671               dest, clip,
7672               0, 0, squareSize, squareSize,
7673               0, 0);
7674   }
7675 }
7676
7677 /* Animate the movement of a single piece */
7678
7679 static void
7680 BeginAnimation (AnimState *anim, ChessSquare piece, int startColor, XPoint *start)
7681 {
7682   Pixmap mask;
7683
7684   if(appData.upsideDown && flipView) piece += piece < BlackPawn ? BlackPawn : -BlackPawn;
7685   /* The old buffer is initialised with the start square (empty) */
7686   BlankSquare(start->x, start->y, startColor, EmptySquare, anim->saveBuf, 0);
7687   anim->prevFrame = *start;
7688
7689   /* The piece will be drawn using its own bitmap as a matte    */
7690   SelectGCMask(piece, &anim->pieceGC, &anim->outlineGC, &mask);
7691   XSetClipMask(xDisplay, anim->pieceGC, mask);
7692 }
7693
7694 static void
7695 AnimationFrame (AnimState *anim, XPoint *frame, ChessSquare piece)
7696 {
7697   XRectangle updates[4];
7698   XRectangle overlap;
7699   XPoint     pt;
7700   int        count, i;
7701
7702   /* Save what we are about to draw into the new buffer */
7703   XCopyArea(xDisplay, xBoardWindow, anim->newBuf, anim->blitGC,
7704             frame->x, frame->y, squareSize, squareSize,
7705             0, 0);
7706
7707   /* Erase bits of the previous frame */
7708   if (Intersect(&anim->prevFrame, frame, squareSize, &overlap, &pt)) {
7709     /* Where the new frame overlapped the previous,
7710        the contents in newBuf are wrong. */
7711     XCopyArea(xDisplay, anim->saveBuf, anim->newBuf, anim->blitGC,
7712               overlap.x, overlap.y,
7713               overlap.width, overlap.height,
7714               pt.x, pt.y);
7715     /* Repaint the areas in the old that don't overlap new */
7716     CalcUpdateRects(&anim->prevFrame, frame, squareSize, updates, &count);
7717     for (i = 0; i < count; i++)
7718       XCopyArea(xDisplay, anim->saveBuf, xBoardWindow, anim->blitGC,
7719                 updates[i].x - anim->prevFrame.x,
7720                 updates[i].y - anim->prevFrame.y,
7721                 updates[i].width, updates[i].height,
7722                 updates[i].x, updates[i].y);
7723   } else {
7724     /* Easy when no overlap */
7725     XCopyArea(xDisplay, anim->saveBuf, xBoardWindow, anim->blitGC,
7726                   0, 0, squareSize, squareSize,
7727                   anim->prevFrame.x, anim->prevFrame.y);
7728   }
7729
7730   /* Save this frame for next time round */
7731   XCopyArea(xDisplay, anim->newBuf, anim->saveBuf, anim->blitGC,
7732                 0, 0, squareSize, squareSize,
7733                 0, 0);
7734   anim->prevFrame = *frame;
7735
7736   /* Draw piece over original screen contents, not current,
7737      and copy entire rect. Wipes out overlapping piece images. */
7738   OverlayPiece(piece, anim->pieceGC, anim->outlineGC, anim->newBuf);
7739   XCopyArea(xDisplay, anim->newBuf, xBoardWindow, anim->blitGC,
7740                 0, 0, squareSize, squareSize,
7741                 frame->x, frame->y);
7742 }
7743
7744 static void
7745 EndAnimation (AnimState *anim, XPoint *finish)
7746 {
7747   XRectangle updates[4];
7748   XRectangle overlap;
7749   XPoint     pt;
7750   int        count, i;
7751
7752   /* The main code will redraw the final square, so we
7753      only need to erase the bits that don't overlap.    */
7754   if (Intersect(&anim->prevFrame, finish, squareSize, &overlap, &pt)) {
7755     CalcUpdateRects(&anim->prevFrame, finish, squareSize, updates, &count);
7756     for (i = 0; i < count; i++)
7757       XCopyArea(xDisplay, anim->saveBuf, xBoardWindow, anim->blitGC,
7758                 updates[i].x - anim->prevFrame.x,
7759                 updates[i].y - anim->prevFrame.y,
7760                 updates[i].width, updates[i].height,
7761                 updates[i].x, updates[i].y);
7762   } else {
7763     XCopyArea(xDisplay, anim->saveBuf, xBoardWindow, anim->blitGC,
7764                 0, 0, squareSize, squareSize,
7765                 anim->prevFrame.x, anim->prevFrame.y);
7766   }
7767 }
7768
7769 static void
7770 FrameSequence (AnimState *anim, ChessSquare piece, int startColor, XPoint *start, XPoint *finish, XPoint frames[], int nFrames)
7771 {
7772   int n;
7773
7774   BeginAnimation(anim, piece, startColor, start);
7775   for (n = 0; n < nFrames; n++) {
7776     AnimationFrame(anim, &(frames[n]), piece);
7777     FrameDelay(appData.animSpeed);
7778   }
7779   EndAnimation(anim, finish);
7780 }
7781
7782 void
7783 AnimateAtomicCapture (Board board, int fromX, int fromY, int toX, int toY)
7784 {
7785     int i, x, y;
7786     ChessSquare piece = board[fromY][toY];
7787     board[fromY][toY] = EmptySquare;
7788     DrawPosition(FALSE, board);
7789     if (flipView) {
7790         x = lineGap + ((BOARD_WIDTH-1)-toX) * (squareSize + lineGap);
7791         y = lineGap + toY * (squareSize + lineGap);
7792     } else {
7793         x = lineGap + toX * (squareSize + lineGap);
7794         y = lineGap + ((BOARD_HEIGHT-1)-toY) * (squareSize + lineGap);
7795     }
7796     for(i=1; i<4*kFactor; i++) {
7797         int r = squareSize * 9 * i/(20*kFactor - 5);
7798         XFillArc(xDisplay, xBoardWindow, highlineGC,
7799                 x + squareSize/2 - r, y+squareSize/2 - r, 2*r, 2*r, 0, 64*360);
7800         FrameDelay(appData.animSpeed);
7801     }
7802     board[fromY][toY] = piece;
7803 }
7804
7805 /* Main control logic for deciding what to animate and how */
7806
7807 void
7808 AnimateMove (Board board, int fromX, int fromY, int toX, int toY)
7809 {
7810   ChessSquare piece;
7811   int hop;
7812   XPoint      start, finish, mid;
7813   XPoint      frames[kFactor * 2 + 1];
7814   int         nFrames, startColor, endColor;
7815
7816   /* Are we animating? */
7817   if (!appData.animate || appData.blindfold)
7818     return;
7819
7820   if(board[toY][toX] == WhiteRook && board[fromY][fromX] == WhiteKing ||
7821      board[toY][toX] == BlackRook && board[fromY][fromX] == BlackKing)
7822         return; // [HGM] FRC: no animtion of FRC castlings, as to-square is not true to-square
7823
7824   if (fromY < 0 || fromX < 0 || toX < 0 || toY < 0) return;
7825   piece = board[fromY][fromX];
7826   if (piece >= EmptySquare) return;
7827
7828 #if DONT_HOP
7829   hop = FALSE;
7830 #else
7831   hop = abs(fromX-toX) == 1 && abs(fromY-toY) == 2 || abs(fromX-toX) == 2 && abs(fromY-toY) == 1;
7832 #endif
7833
7834   ScreenSquare(fromX, fromY, &start, &startColor);
7835   ScreenSquare(toX, toY, &finish, &endColor);
7836
7837   if (hop) {
7838     /* Knight: make straight movement then diagonal */
7839     if (abs(toY - fromY) < abs(toX - fromX)) {
7840        mid.x = start.x + (finish.x - start.x) / 2;
7841        mid.y = start.y;
7842      } else {
7843        mid.x = start.x;
7844        mid.y = start.y + (finish.y - start.y) / 2;
7845      }
7846   } else {
7847     mid.x = start.x + (finish.x - start.x) / 2;
7848     mid.y = start.y + (finish.y - start.y) / 2;
7849   }
7850
7851   /* Don't use as many frames for very short moves */
7852   if (abs(toY - fromY) + abs(toX - fromX) <= 2)
7853     Tween(&start, &mid, &finish, kFactor - 1, frames, &nFrames);
7854   else
7855     Tween(&start, &mid, &finish, kFactor, frames, &nFrames);
7856   FrameSequence(&game, piece, startColor, &start, &finish, frames, nFrames);
7857   if(Explode(board, fromX, fromY, toX, toY)) { // mark as damaged
7858     int i,j;
7859     for(i=0; i<BOARD_WIDTH; i++) for(j=0; j<BOARD_HEIGHT; j++)
7860       if((i-toX)*(i-toX) + (j-toY)*(j-toY) < 6) damage[0][j][i] = True;
7861   }
7862
7863   /* Be sure end square is redrawn */
7864   damage[0][toY][toX] = True;
7865 }
7866
7867 void
7868 DragPieceBegin (int x, int y, Boolean instantly)
7869 {
7870     int  boardX, boardY, color;
7871     XPoint corner;
7872
7873     /* Are we animating? */
7874     if (!appData.animateDragging || appData.blindfold)
7875       return;
7876
7877     /* Figure out which square we start in and the
7878        mouse position relative to top left corner. */
7879     BoardSquare(x, y, &boardX, &boardY);
7880     player.startBoardX = boardX;
7881     player.startBoardY = boardY;
7882     ScreenSquare(boardX, boardY, &corner, &color);
7883     player.startSquare  = corner;
7884     player.startColor   = color;
7885     /* As soon as we start dragging, the piece will jump slightly to
7886        be centered over the mouse pointer. */
7887     player.mouseDelta.x = squareSize/2;
7888     player.mouseDelta.y = squareSize/2;
7889     /* Initialise animation */
7890     player.dragPiece = PieceForSquare(boardX, boardY);
7891     /* Sanity check */
7892     if (player.dragPiece >= 0 && player.dragPiece < EmptySquare) {
7893         player.dragActive = True;
7894         BeginAnimation(&player, player.dragPiece, color, &corner);
7895         /* Mark this square as needing to be redrawn. Note that
7896            we don't remove the piece though, since logically (ie
7897            as seen by opponent) the move hasn't been made yet. */
7898            if(boardX == BOARD_RGHT+1 && PieceForSquare(boardX-1, boardY) > 1 ||
7899               boardX == BOARD_LEFT-2 && PieceForSquare(boardX+1, boardY) > 1)
7900            XCopyArea(xDisplay, xBoardWindow, player.saveBuf, player.blitGC,
7901                      corner.x, corner.y, squareSize, squareSize,
7902                      0, 0); // [HGM] zh: unstack in stead of grab
7903            if(gatingPiece != EmptySquare) {
7904                /* Kludge alert: When gating we want the introduced
7905                   piece to appear on the from square. To generate an
7906                   image of it, we draw it on the board, copy the image,
7907                   and draw the original piece again. */
7908                ChessSquare piece = boards[currentMove][boardY][boardX];
7909                DrawSquare(boardY, boardX, gatingPiece, 0);
7910                XCopyArea(xDisplay, xBoardWindow, player.saveBuf, player.blitGC,
7911                      corner.x, corner.y, squareSize, squareSize, 0, 0);
7912                DrawSquare(boardY, boardX, piece, 0);
7913            }
7914         damage[0][boardY][boardX] = True;
7915     } else {
7916         player.dragActive = False;
7917     }
7918 }
7919
7920 void
7921 ChangeDragPiece (ChessSquare piece)
7922 {
7923   Pixmap mask;
7924   player.dragPiece = piece;
7925   /* The piece will be drawn using its own bitmap as a matte    */
7926   SelectGCMask(piece, &player.pieceGC, &player.outlineGC, &mask);
7927   XSetClipMask(xDisplay, player.pieceGC, mask);
7928 }
7929
7930 static void
7931 DragPieceMove (int x, int y)
7932 {
7933     XPoint corner;
7934
7935     /* Are we animating? */
7936     if (!appData.animateDragging || appData.blindfold)
7937       return;
7938
7939     /* Sanity check */
7940     if (! player.dragActive)
7941       return;
7942     /* Move piece, maintaining same relative position
7943        of mouse within square    */
7944     corner.x = x - player.mouseDelta.x;
7945     corner.y = y - player.mouseDelta.y;
7946     AnimationFrame(&player, &corner, player.dragPiece);
7947 #if HIGHDRAG*0
7948     if (appData.highlightDragging) {
7949         int boardX, boardY;
7950         BoardSquare(x, y, &boardX, &boardY);
7951         SetHighlights(fromX, fromY, boardX, boardY);
7952     }
7953 #endif
7954 }
7955
7956 void
7957 DragPieceEnd (int x, int y)
7958 {
7959     int boardX, boardY, color;
7960     XPoint corner;
7961
7962     /* Are we animating? */
7963     if (!appData.animateDragging || appData.blindfold)
7964       return;
7965
7966     /* Sanity check */
7967     if (! player.dragActive)
7968       return;
7969     /* Last frame in sequence is square piece is
7970        placed on, which may not match mouse exactly. */
7971     BoardSquare(x, y, &boardX, &boardY);
7972     ScreenSquare(boardX, boardY, &corner, &color);
7973     EndAnimation(&player, &corner);
7974
7975     /* Be sure end square is redrawn */
7976     damage[0][boardY][boardX] = True;
7977
7978     /* This prevents weird things happening with fast successive
7979        clicks which on my Sun at least can cause motion events
7980        without corresponding press/release. */
7981     player.dragActive = False;
7982 }
7983
7984 /* Handle expose event while piece being dragged */
7985
7986 static void
7987 DrawDragPiece ()
7988 {
7989   if (!player.dragActive || appData.blindfold)
7990     return;
7991
7992   /* What we're doing: logically, the move hasn't been made yet,
7993      so the piece is still in it's original square. But visually
7994      it's being dragged around the board. So we erase the square
7995      that the piece is on and draw it at the last known drag point. */
7996   BlankSquare(player.startSquare.x, player.startSquare.y,
7997                 player.startColor, EmptySquare, xBoardWindow, 1);
7998   AnimationFrame(&player, &player.prevFrame, player.dragPiece);
7999   damage[0][player.startBoardY][player.startBoardX] = TRUE;
8000 }
8001
8002 #include <sys/ioctl.h>
8003 int
8004 get_term_width ()
8005 {
8006     int fd, default_width;
8007
8008     fd = STDIN_FILENO;
8009     default_width = 79; // this is FICS default anyway...
8010
8011 #if !defined(TIOCGWINSZ) && defined(TIOCGSIZE)
8012     struct ttysize win;
8013     if (!ioctl(fd, TIOCGSIZE, &win))
8014         default_width = win.ts_cols;
8015 #elif defined(TIOCGWINSZ)
8016     struct winsize win;
8017     if (!ioctl(fd, TIOCGWINSZ, &win))
8018         default_width = win.ws_col;
8019 #endif
8020     return default_width;
8021 }
8022
8023 void
8024 update_ics_width ()
8025 {
8026   static int old_width = 0;
8027   int new_width = get_term_width();
8028
8029   if (old_width != new_width)
8030     ics_printf("set width %d\n", new_width);
8031   old_width = new_width;
8032 }
8033
8034 void
8035 NotifyFrontendLogin ()
8036 {
8037     update_ics_width();
8038 }
8039
8040 /* [AS] Arrow highlighting support */
8041
8042 static double A_WIDTH = 5; /* Width of arrow body */
8043
8044 #define A_HEIGHT_FACTOR 6   /* Length of arrow "point", relative to body width */
8045 #define A_WIDTH_FACTOR  3   /* Width of arrow "point", relative to body width */
8046
8047 static double
8048 Sqr (double x)
8049 {
8050     return x*x;
8051 }
8052
8053 static int
8054 Round (double x)
8055 {
8056     return (int) (x + 0.5);
8057 }
8058
8059 void
8060 SquareToPos (int rank, int file, int *x, int *y)
8061 {
8062     if (flipView) {
8063         *x = lineGap + ((BOARD_WIDTH-1)-file) * (squareSize + lineGap);
8064         *y = lineGap + rank * (squareSize + lineGap);
8065     } else {
8066         *x = lineGap + file * (squareSize + lineGap);
8067         *y = lineGap + ((BOARD_HEIGHT-1)-rank) * (squareSize + lineGap);
8068     }
8069 }
8070
8071 /* Draw an arrow between two points using current settings */
8072 void
8073 DrawArrowBetweenPoints (int s_x, int s_y, int d_x, int d_y)
8074 {
8075     XPoint arrow[8];
8076     double dx, dy, j, k, x, y;
8077
8078     if( d_x == s_x ) {
8079         int h = (d_y > s_y) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
8080
8081         arrow[0].x = s_x + A_WIDTH + 0.5;
8082         arrow[0].y = s_y;
8083
8084         arrow[1].x = s_x + A_WIDTH + 0.5;
8085         arrow[1].y = d_y - h;
8086
8087         arrow[2].x = arrow[1].x + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
8088         arrow[2].y = d_y - h;
8089
8090         arrow[3].x = d_x;
8091         arrow[3].y = d_y;
8092
8093         arrow[5].x = arrow[1].x - 2*A_WIDTH + 0.5;
8094         arrow[5].y = d_y - h;
8095
8096         arrow[4].x = arrow[5].x - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
8097         arrow[4].y = d_y - h;
8098
8099         arrow[6].x = arrow[1].x - 2*A_WIDTH + 0.5;
8100         arrow[6].y = s_y;
8101     }
8102     else if( d_y == s_y ) {
8103         int w = (d_x > s_x) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
8104
8105         arrow[0].x = s_x;
8106         arrow[0].y = s_y + A_WIDTH + 0.5;
8107
8108         arrow[1].x = d_x - w;
8109         arrow[1].y = s_y + A_WIDTH + 0.5;
8110
8111         arrow[2].x = d_x - w;
8112         arrow[2].y = arrow[1].y + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
8113
8114         arrow[3].x = d_x;
8115         arrow[3].y = d_y;
8116
8117         arrow[5].x = d_x - w;
8118         arrow[5].y = arrow[1].y - 2*A_WIDTH + 0.5;
8119
8120         arrow[4].x = d_x - w;
8121         arrow[4].y = arrow[5].y - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
8122
8123         arrow[6].x = s_x;
8124         arrow[6].y = arrow[1].y - 2*A_WIDTH + 0.5;
8125     }
8126     else {
8127         /* [AS] Needed a lot of paper for this! :-) */
8128         dy = (double) (d_y - s_y) / (double) (d_x - s_x);
8129         dx = (double) (s_x - d_x) / (double) (s_y - d_y);
8130
8131         j = sqrt( Sqr(A_WIDTH) / (1.0 + Sqr(dx)) );
8132
8133         k = sqrt( Sqr(A_WIDTH*A_HEIGHT_FACTOR) / (1.0 + Sqr(dy)) );
8134
8135         x = s_x;
8136         y = s_y;
8137
8138         arrow[0].x = Round(x - j);
8139         arrow[0].y = Round(y + j*dx);
8140
8141         arrow[1].x = Round(arrow[0].x + 2*j);   // [HGM] prevent width to be affected by rounding twice
8142         arrow[1].y = Round(arrow[0].y - 2*j*dx);
8143
8144         if( d_x > s_x ) {
8145             x = (double) d_x - k;
8146             y = (double) d_y - k*dy;
8147         }
8148         else {
8149             x = (double) d_x + k;
8150             y = (double) d_y + k*dy;
8151         }
8152
8153         x = Round(x); y = Round(y); // [HGM] make sure width of shaft is rounded the same way on both ends
8154
8155         arrow[6].x = Round(x - j);
8156         arrow[6].y = Round(y + j*dx);
8157
8158         arrow[2].x = Round(arrow[6].x + 2*j);
8159         arrow[2].y = Round(arrow[6].y - 2*j*dx);
8160
8161         arrow[3].x = Round(arrow[2].x + j*(A_WIDTH_FACTOR-1));
8162         arrow[3].y = Round(arrow[2].y - j*(A_WIDTH_FACTOR-1)*dx);
8163
8164         arrow[4].x = d_x;
8165         arrow[4].y = d_y;
8166
8167         arrow[5].x = Round(arrow[6].x - j*(A_WIDTH_FACTOR-1));
8168         arrow[5].y = Round(arrow[6].y + j*(A_WIDTH_FACTOR-1)*dx);
8169     }
8170
8171     XFillPolygon(xDisplay, xBoardWindow, highlineGC, arrow, 7, Nonconvex, CoordModeOrigin);
8172     if(appData.monoMode) arrow[7] = arrow[0], XDrawLines(xDisplay, xBoardWindow, darkSquareGC, arrow, 8, CoordModeOrigin);
8173 //    Polygon( hdc, arrow, 7 );
8174 }
8175
8176 void
8177 ArrowDamage (int s_col, int s_row, int d_col, int d_row)
8178 {
8179     int hor, vert, i;
8180     hor = 64*s_col + 32; vert = 64*s_row + 32;
8181     for(i=0; i<= 64; i++) {
8182             damage[0][vert+6>>6][hor+6>>6] = True;
8183             damage[0][vert-6>>6][hor+6>>6] = True;
8184             damage[0][vert+6>>6][hor-6>>6] = True;
8185             damage[0][vert-6>>6][hor-6>>6] = True;
8186             hor += d_col - s_col; vert += d_row - s_row;
8187     }
8188 }
8189
8190 /* [AS] Draw an arrow between two squares */
8191 void
8192 DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row)
8193 {
8194     int s_x, s_y, d_x, d_y;
8195
8196     if( s_col == d_col && s_row == d_row ) {
8197         return;
8198     }
8199
8200     /* Get source and destination points */
8201     SquareToPos( s_row, s_col, &s_x, &s_y);
8202     SquareToPos( d_row, d_col, &d_x, &d_y);
8203
8204     if( d_y > s_y ) {
8205         d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides!
8206     }
8207     else if( d_y < s_y ) {
8208         d_y += squareSize / 2 + squareSize / 4;
8209     }
8210     else {
8211         d_y += squareSize / 2;
8212     }
8213
8214     if( d_x > s_x ) {
8215         d_x += squareSize / 2 - squareSize / 4;
8216     }
8217     else if( d_x < s_x ) {
8218         d_x += squareSize / 2 + squareSize / 4;
8219     }
8220     else {
8221         d_x += squareSize / 2;
8222     }
8223
8224     s_x += squareSize / 2;
8225     s_y += squareSize / 2;
8226
8227     /* Adjust width */
8228     A_WIDTH = squareSize / 14.; //[HGM] make float
8229
8230     DrawArrowBetweenPoints( s_x, s_y, d_x, d_y );
8231     ArrowDamage(s_col, s_row, d_col, d_row);
8232 }
8233
8234 Boolean
8235 IsDrawArrowEnabled ()
8236 {
8237     return appData.highlightMoveWithArrow && squareSize >= 32;
8238 }
8239
8240 void
8241 DrawArrowHighlight (int fromX, int fromY, int toX,int toY)
8242 {
8243     if( IsDrawArrowEnabled() && fromX >= 0 && fromY >= 0 && toX >= 0 && toY >= 0)
8244         DrawArrowBetweenSquares(fromX, fromY, toX, toY);
8245 }
8246
8247 void
8248 UpdateLogos (int displ)
8249 {
8250     return; // no logos in XBoard yet
8251 }
8252