Switch to use of short menu references
[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, "Always Queen"),
2076                     args, 1);
2077     }
2078     if (appData.animateDragging) {
2079         XtSetValues(XtNameToWidget(menuBarWidget,
2080                                    "Animate Dragging"),
2081                     args, 1);
2082     }
2083     if (appData.animate) {
2084         XtSetValues(XtNameToWidget(menuBarWidget, "Animate Moving"),
2085                     args, 1);
2086     }
2087     if (appData.autoCallFlag) {
2088         XtSetValues(XtNameToWidget(menuBarWidget, "Auto Flag"),
2089                     args, 1);
2090     }
2091     if (appData.autoFlipView) {
2092         XtSetValues(XtNameToWidget(menuBarWidget,"Auto Flip View"),
2093                     args, 1);
2094     }
2095     if (appData.blindfold) {
2096         XtSetValues(XtNameToWidget(menuBarWidget,
2097                                    "Blindfold"), args, 1);
2098     }
2099     if (appData.flashCount > 0) {
2100         XtSetValues(XtNameToWidget(menuBarWidget,
2101                                    "Flash Moves"),
2102                     args, 1);
2103     }
2104 #if HIGHDRAG
2105     if (appData.highlightDragging) {
2106         XtSetValues(XtNameToWidget(menuBarWidget,
2107                                    "Highlight Dragging"),
2108                     args, 1);
2109     }
2110 #endif
2111     if (appData.highlightLastMove) {
2112         XtSetValues(XtNameToWidget(menuBarWidget,
2113                                    "Highlight Last Move"),
2114                     args, 1);
2115     }
2116     if (appData.highlightMoveWithArrow) {
2117         XtSetValues(XtNameToWidget(menuBarWidget,
2118                                    "Arrow"),
2119                     args, 1);
2120     }
2121 //    if (appData.icsAlarm) {
2122 //      XtSetValues(XtNameToWidget(menuBarWidget, "ICS Alarm"),
2123 //                  args, 1);
2124 //    }
2125     if (appData.ringBellAfterMoves) {
2126         XtSetValues(XtNameToWidget(menuBarWidget, "Move Sound"),
2127                     args, 1);
2128     }
2129     if (appData.oneClick) {
2130         XtSetValues(XtNameToWidget(menuBarWidget,
2131                                    "OneClick"), args, 1);
2132     }
2133     if (appData.periodicUpdates) {
2134         XtSetValues(XtNameToWidget(menuBarWidget,
2135                                    "Periodic Updates"), args, 1);
2136     }
2137     if (appData.ponderNextMove) {
2138         XtSetValues(XtNameToWidget(menuBarWidget,
2139                                    "Ponder Next Move"), args, 1);
2140     }
2141     if (appData.popupExitMessage) {
2142         XtSetValues(XtNameToWidget(menuBarWidget,
2143                                    "Popup Exit Message"), args, 1);
2144     }
2145     if (appData.popupMoveErrors) {
2146         XtSetValues(XtNameToWidget(menuBarWidget,
2147                                    "Popup Move Errors"), args, 1);
2148     }
2149 //    if (appData.premove) {
2150 //      XtSetValues(XtNameToWidget(menuBarWidget,
2151 //                                 "Premove"), args, 1);
2152 //    }
2153     if (appData.showCoords) {
2154         XtSetValues(XtNameToWidget(menuBarWidget, "Show Coords"),
2155                     args, 1);
2156     }
2157     if (appData.hideThinkingFromHuman) {
2158         XtSetValues(XtNameToWidget(menuBarWidget, "Hide Thinking"),
2159                     args, 1);
2160     }
2161     if (appData.testLegality) {
2162         XtSetValues(XtNameToWidget(menuBarWidget,"Test Legality"),
2163                     args, 1);
2164     }
2165 #endif
2166     if (saveSettingsOnExit) {
2167         MarkMenuItem("Save Settings on Exit", True);
2168     }
2169
2170     /*
2171      * Create an icon.
2172      */
2173     ReadBitmap(&wIconPixmap, "icon_white.bm",
2174                icon_white_bits, icon_white_width, icon_white_height);
2175     ReadBitmap(&bIconPixmap, "icon_black.bm",
2176                icon_black_bits, icon_black_width, icon_black_height);
2177     iconPixmap = wIconPixmap;
2178     i = 0;
2179     XtSetArg(args[i], XtNiconPixmap, iconPixmap);  i++;
2180     XtSetValues(shellWidget, args, i);
2181
2182     /*
2183      * Create a cursor for the board widget.
2184      */
2185     window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
2186     XChangeWindowAttributes(xDisplay, xBoardWindow,
2187                             CWCursor, &window_attributes);
2188
2189     /*
2190      * Inhibit shell resizing.
2191      */
2192     shellArgs[0].value = (XtArgVal) &w;
2193     shellArgs[1].value = (XtArgVal) &h;
2194     XtGetValues(shellWidget, shellArgs, 2);
2195     shellArgs[4].value = shellArgs[2].value = w;
2196     shellArgs[5].value = shellArgs[3].value = h;
2197     XtSetValues(shellWidget, &shellArgs[2], 4);
2198     marginW =  w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
2199     marginH =  h - boardHeight;
2200
2201     CatchDeleteWindow(shellWidget, "QuitProc");
2202
2203     CreateGCs(False);
2204     CreateGrid();
2205     CreateAnyPieces();
2206
2207     CreatePieceMenus();
2208
2209     if (appData.animate || appData.animateDragging)
2210       CreateAnimVars();
2211
2212     XtAugmentTranslations(formWidget,
2213                           XtParseTranslationTable(globalTranslations));
2214     XtAugmentTranslations(boardWidget,
2215                           XtParseTranslationTable(boardTranslations));
2216     XtAugmentTranslations(whiteTimerWidget,
2217                           XtParseTranslationTable(whiteTranslations));
2218     XtAugmentTranslations(blackTimerWidget,
2219                           XtParseTranslationTable(blackTranslations));
2220
2221     /* Why is the following needed on some versions of X instead
2222      * of a translation? */
2223     XtAddEventHandler(boardWidget, ExposureMask|PointerMotionMask, False,
2224                       (XtEventHandler) EventProc, NULL);
2225     /* end why */
2226     XtAddEventHandler(formWidget, KeyPressMask, False,
2227                       (XtEventHandler) MoveTypeInProc, NULL);
2228     XtAddEventHandler(shellWidget, StructureNotifyMask, False,
2229                       (XtEventHandler) EventProc, NULL);
2230
2231     /* [AS] Restore layout */
2232     if( wpMoveHistory.visible ) {
2233       HistoryPopUp();
2234     }
2235
2236     if( wpEvalGraph.visible )
2237       {
2238         EvalGraphPopUp();
2239       };
2240
2241     if( wpEngineOutput.visible ) {
2242       EngineOutputPopUp();
2243     }
2244
2245     InitBackEnd2();
2246
2247     if (errorExitStatus == -1) {
2248         if (appData.icsActive) {
2249             /* We now wait until we see "login:" from the ICS before
2250                sending the logon script (problems with timestamp otherwise) */
2251             /*ICSInitScript();*/
2252             if (appData.icsInputBox) ICSInputBoxPopUp();
2253         }
2254
2255     #ifdef SIGWINCH
2256     signal(SIGWINCH, TermSizeSigHandler);
2257     #endif
2258         signal(SIGINT, IntSigHandler);
2259         signal(SIGTERM, IntSigHandler);
2260         if (*appData.cmailGameName != NULLCHAR) {
2261             signal(SIGUSR1, CmailSigHandler);
2262         }
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 void
2365 GreyRevert (Boolean grey)
2366 {
2367     MarkMenuItem("Revert", !grey);
2368     MarkMenuItem("Annotate", !grey);
2369 }
2370
2371 Enables icsEnables[] = {
2372     { "Mail Move", False },
2373     { "Reload CMail Message", False },
2374     { "Machine Black", False },
2375     { "Machine White", False },
2376     { "Analysis Mode", False },
2377     { "Analyze File", False },
2378     { "Two Machines", False },
2379     { "Machine Match", False },
2380 #ifndef ZIPPY
2381     { "Hint", False },
2382     { "Book", False },
2383     { "Move Now", False },
2384 #ifndef OPTIONSDIALOG
2385     { "Periodic Updates", False },
2386     { "Hide Thinking", False },
2387     { "Ponder Next Move", False },
2388 #endif
2389 #endif
2390     { "Engine #1 Settings", False },
2391     { "Engine #2 Settings", False },
2392     { "Load Engine", False },
2393     { "Annotate", False },
2394     { "Match", False },
2395     { NULL, False }
2396 };
2397
2398 Enables ncpEnables[] = {
2399     { "Mail Move", False },
2400     { "Reload CMail Message", False },
2401     { "Machine White", False },
2402     { "Machine Black", False },
2403     { "Analysis Mode", False },
2404     { "Analyze File", False },
2405     { "Two Machines", False },
2406     { "Machine Match", False },
2407     { "ICS Client", False },
2408     { "ICStex", False },
2409     { "ICS Input Box", False },
2410     { "Action", False },
2411     { "Revert", False },
2412     { "Annotate", False },
2413     { "Engine #1 Settings", False },
2414     { "Engine #2 Settings", False },
2415     { "Move Now", False },
2416     { "Retract Move", False },
2417     { "ICS", False },
2418 #ifndef OPTIONSDIALOG
2419     { "Auto Flag", False },
2420     { "Auto Flip View", False },
2421 //    { "ICS Alarm", False },
2422     { "Move Sound", False },
2423     { "Hide Thinking", False },
2424     { "Periodic Updates", False },
2425     { "Ponder Next Move", False },
2426 #endif
2427     { "Hint", False },
2428     { "Book", False },
2429     { NULL, False }
2430 };
2431
2432 Enables gnuEnables[] = {
2433     { "ICS Client", False },
2434     { "ICStex", False },
2435     { "ICS Input Box", False },
2436     { "Accept", False },
2437     { "Decline", False },
2438     { "Rematch", False },
2439     { "Adjourn", False },
2440     { "Stop Examining", False },
2441     { "Stop Observing", False },
2442     { "Upload to Examine", False },
2443     { "Revert", False },
2444     { "Annotate", False },
2445     { "ICS", False },
2446
2447     /* The next two options rely on SetCmailMode being called *after*    */
2448     /* SetGNUMode so that when GNU is being used to give hints these     */
2449     /* menu options are still available                                  */
2450
2451     { "Mail Move", False },
2452     { "Reload CMail Message", False },
2453     // [HGM] The following have been added to make a switch from ncp to GNU mode possible
2454     { "Machine White", True },
2455     { "Machine Black", True },
2456     { "Analysis Mode", True },
2457     { "Analyze File", True },
2458     { "Two Machines", True },
2459     { "Machine Match", True },
2460     { "Engine #1 Settings", True },
2461     { "Engine #2 Settings", True },
2462     { "Hint", True },
2463     { "Book", True },
2464     { "Move Now", True },
2465     { "Retract Move", True },
2466     { "Action", True },
2467     { NULL, False }
2468 };
2469
2470 Enables cmailEnables[] = {
2471     { "Action", True },
2472     { "Call Flag", False },
2473     { "Draw", True },
2474     { "Adjourn", False },
2475     { "Abort", False },
2476     { "Stop Observing", False },
2477     { "Stop Examining", False },
2478     { "Mail Move", True },
2479     { "Reload CMail Message", True },
2480     { NULL, False }
2481 };
2482
2483 Enables trainingOnEnables[] = {
2484   { "Edit Comment", False },
2485   { "Pause", False },
2486   { "Forward", False },
2487   { "Backward", False },
2488   { "Forward to End", False },
2489   { "Back to Start", False },
2490   { "Move Now", False },
2491   { "Truncate Game", False },
2492   { NULL, False }
2493 };
2494
2495 Enables trainingOffEnables[] = {
2496   { "Edit Comment", True },
2497   { "Pause", True },
2498   { "Forward", True },
2499   { "Backward", True },
2500   { "Forward to End", True },
2501   { "Back to Start", True },
2502   { "Move Now", True },
2503   { "Truncate Game", True },
2504   { NULL, False }
2505 };
2506
2507 Enables machineThinkingEnables[] = {
2508   { "Load Game", False },
2509 //  { "Load Next Game", False },
2510 //  { "Load Previous Game", False },
2511 //  { "Reload Same Game", False },
2512   { "Paste Game", False },
2513   { "Load Position", False },
2514 //  { "Load Next Position", False },
2515 //  { "Load Previous Position", False },
2516 //  { "Reload Same Position", False },
2517   { "Paste Position", False },
2518   { "Machine White", False },
2519   { "Machine Black", False },
2520   { "Two Machines", False },
2521 //  { "Machine Match", False },
2522   { "Retract Move", False },
2523   { NULL, False }
2524 };
2525
2526 Enables userThinkingEnables[] = {
2527   { "Load Game", True },
2528 //  { "Load Next Game", True },
2529 //  { "Load Previous Game", True },
2530 //  { "Reload Same Game", True },
2531   { "Paste Game", True },
2532   { "Load Position", True },
2533 //  { "Load Next Position", True },
2534 //  { "Load Previous Position", True },
2535 //  { "Reload Same Position", True },
2536   { "Paste Position", True },
2537   { "Machine White", True },
2538   { "Machine Black", True },
2539   { "Two Machines", True },
2540 //  { "Machine Match", True },
2541   { "Retract Move", True },
2542   { NULL, False }
2543 };
2544
2545 void
2546 SetICSMode ()
2547 {
2548   SetMenuEnables(icsEnables);
2549
2550 #if ZIPPY
2551   if (appData.zippyPlay && !appData.noChessProgram) { /* [DM] icsEngineAnalyze */
2552      EnableMenuItem("Analysis Mode", True);
2553      EnableMenuItem("Engine #1 Settings", True);
2554   }
2555 #endif
2556 }
2557
2558 void
2559 SetNCPMode ()
2560 {
2561   SetMenuEnables(ncpEnables);
2562 }
2563
2564 void
2565 SetGNUMode ()
2566 {
2567   SetMenuEnables(gnuEnables);
2568 }
2569
2570 void
2571 SetCmailMode ()
2572 {
2573   SetMenuEnables(cmailEnables);
2574 }
2575
2576 void
2577 SetTrainingModeOn ()
2578 {
2579   SetMenuEnables(trainingOnEnables);
2580   if (appData.showButtonBar) {
2581     XtSetSensitive(buttonBarWidget, False);
2582   }
2583   CommentPopDown();
2584 }
2585
2586 void
2587 SetTrainingModeOff ()
2588 {
2589   SetMenuEnables(trainingOffEnables);
2590   if (appData.showButtonBar) {
2591     XtSetSensitive(buttonBarWidget, True);
2592   }
2593 }
2594
2595 void
2596 SetUserThinkingEnables ()
2597 {
2598   if (appData.noChessProgram) return;
2599   SetMenuEnables(userThinkingEnables);
2600 }
2601
2602 void
2603 SetMachineThinkingEnables ()
2604 {
2605   if (appData.noChessProgram) return;
2606   SetMenuEnables(machineThinkingEnables);
2607   switch (gameMode) {
2608   case MachinePlaysBlack:
2609   case MachinePlaysWhite:
2610   case TwoMachinesPlay:
2611     EnableMenuItem(ModeToWidgetName(gameMode), True);
2612     break;
2613   default:
2614     break;
2615   }
2616 }
2617
2618 // [HGM] code borrowed from winboard.c (which should thus go to backend.c!)
2619 #define HISTORY_SIZE 64
2620 static char *history[HISTORY_SIZE];
2621 int histIn = 0, histP = 0;
2622
2623 void
2624 SaveInHistory (char *cmd)
2625 {
2626   if (history[histIn] != NULL) {
2627     free(history[histIn]);
2628     history[histIn] = NULL;
2629   }
2630   if (*cmd == NULLCHAR) return;
2631   history[histIn] = StrSave(cmd);
2632   histIn = (histIn + 1) % HISTORY_SIZE;
2633   if (history[histIn] != NULL) {
2634     free(history[histIn]);
2635     history[histIn] = NULL;
2636   }
2637   histP = histIn;
2638 }
2639
2640 char *
2641 PrevInHistory (char *cmd)
2642 {
2643   int newhp;
2644   if (histP == histIn) {
2645     if (history[histIn] != NULL) free(history[histIn]);
2646     history[histIn] = StrSave(cmd);
2647   }
2648   newhp = (histP - 1 + HISTORY_SIZE) % HISTORY_SIZE;
2649   if (newhp == histIn || history[newhp] == NULL) return NULL;
2650   histP = newhp;
2651   return history[histP];
2652 }
2653
2654 char *
2655 NextInHistory ()
2656 {
2657   if (histP == histIn) return NULL;
2658   histP = (histP + 1) % HISTORY_SIZE;
2659   return history[histP];   
2660 }
2661 // end of borrowed code
2662
2663 #define Abs(n) ((n)<0 ? -(n) : (n))
2664
2665 #ifdef ENABLE_NLS
2666 char *
2667 InsertPxlSize (char *pattern, int targetPxlSize)
2668 {
2669     char *base_fnt_lst, strInt[12], *p, *q;
2670     int alternatives, i, len, strIntLen;
2671
2672     /*
2673      * Replace the "*" (if present) in the pixel-size slot of each
2674      * alternative with the targetPxlSize.
2675      */
2676     p = pattern;
2677     alternatives = 1;
2678     while ((p = strchr(p, ',')) != NULL) {
2679       alternatives++;
2680       p++;
2681     }
2682     snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
2683     strIntLen = strlen(strInt);
2684     base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
2685
2686     p = pattern;
2687     q = base_fnt_lst;
2688     while (alternatives--) {
2689       char *comma = strchr(p, ',');
2690       for (i=0; i<14; i++) {
2691         char *hyphen = strchr(p, '-');
2692         if (!hyphen) break;
2693         if (comma && hyphen > comma) break;
2694         len = hyphen + 1 - p;
2695         if (i == 7 && *p == '*' && len == 2) {
2696           p += len;
2697           memcpy(q, strInt, strIntLen);
2698           q += strIntLen;
2699           *q++ = '-';
2700         } else {
2701           memcpy(q, p, len);
2702           p += len;
2703           q += len;
2704         }
2705       }
2706       if (!comma) break;
2707       len = comma + 1 - p;
2708       memcpy(q, p, len);
2709       p += len;
2710       q += len;
2711     }
2712     strcpy(q, p);
2713
2714     return base_fnt_lst;
2715 }
2716
2717 XFontSet
2718 CreateFontSet (char *base_fnt_lst)
2719 {
2720     XFontSet fntSet;
2721     char **missing_list;
2722     int missing_count;
2723     char *def_string;
2724
2725     fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
2726                             &missing_list, &missing_count, &def_string);
2727     if (appData.debugMode) {
2728       int i, count;
2729       XFontStruct **font_struct_list;
2730       char **font_name_list;
2731       fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
2732       if (fntSet) {
2733         fprintf(debugFP, " got list %s, locale %s\n",
2734                 XBaseFontNameListOfFontSet(fntSet),
2735                 XLocaleOfFontSet(fntSet));
2736         count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
2737         for (i = 0; i < count; i++) {
2738           fprintf(debugFP, " got charset %s\n", font_name_list[i]);
2739         }
2740       }
2741       for (i = 0; i < missing_count; i++) {
2742         fprintf(debugFP, " missing charset %s\n", missing_list[i]);
2743       }
2744     }
2745     if (fntSet == NULL) {
2746       fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
2747       exit(2);
2748     }
2749     return fntSet;
2750 }
2751 #else // not ENABLE_NLS
2752 /*
2753  * Find a font that matches "pattern" that is as close as
2754  * possible to the targetPxlSize.  Prefer fonts that are k
2755  * pixels smaller to fonts that are k pixels larger.  The
2756  * pattern must be in the X Consortium standard format,
2757  * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
2758  * The return value should be freed with XtFree when no
2759  * longer needed.
2760  */
2761 char *
2762 FindFont (char *pattern, int targetPxlSize)
2763 {
2764     char **fonts, *p, *best, *scalable, *scalableTail;
2765     int i, j, nfonts, minerr, err, pxlSize;
2766
2767     fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
2768     if (nfonts < 1) {
2769         fprintf(stderr, _("%s: no fonts match pattern %s\n"),
2770                 programName, pattern);
2771         exit(2);
2772     }
2773
2774     best = fonts[0];
2775     scalable = NULL;
2776     minerr = 999999;
2777     for (i=0; i<nfonts; i++) {
2778         j = 0;
2779         p = fonts[i];
2780         if (*p != '-') continue;
2781         while (j < 7) {
2782             if (*p == NULLCHAR) break;
2783             if (*p++ == '-') j++;
2784         }
2785         if (j < 7) continue;
2786         pxlSize = atoi(p);
2787         if (pxlSize == 0) {
2788             scalable = fonts[i];
2789             scalableTail = p;
2790         } else {
2791             err = pxlSize - targetPxlSize;
2792             if (Abs(err) < Abs(minerr) ||
2793                 (minerr > 0 && err < 0 && -err == minerr)) {
2794                 best = fonts[i];
2795                 minerr = err;
2796             }
2797         }
2798     }
2799     if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
2800         /* If the error is too big and there is a scalable font,
2801            use the scalable font. */
2802         int headlen = scalableTail - scalable;
2803         p = (char *) XtMalloc(strlen(scalable) + 10);
2804         while (isdigit(*scalableTail)) scalableTail++;
2805         sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
2806     } else {
2807         p = (char *) XtMalloc(strlen(best) + 2);
2808         safeStrCpy(p, best, strlen(best)+1 );
2809     }
2810     if (appData.debugMode) {
2811         fprintf(debugFP, _("resolved %s at pixel size %d\n  to %s\n"),
2812                 pattern, targetPxlSize, p);
2813     }
2814     XFreeFontNames(fonts);
2815     return p;
2816 }
2817 #endif
2818
2819 void
2820 DeleteGCs ()
2821 {   // [HGM] deletes GCs that are to be remade, to prevent resource leak;
2822     // must be called before all non-first callse to CreateGCs()
2823     XtReleaseGC(shellWidget, highlineGC);
2824     XtReleaseGC(shellWidget, lightSquareGC);
2825     XtReleaseGC(shellWidget, darkSquareGC);
2826     XtReleaseGC(shellWidget, lineGC);
2827     if (appData.monoMode) {
2828         if (DefaultDepth(xDisplay, xScreen) == 1) {
2829             XtReleaseGC(shellWidget, wbPieceGC);
2830         } else {
2831             XtReleaseGC(shellWidget, bwPieceGC);
2832         }
2833     } else {
2834         XtReleaseGC(shellWidget, prelineGC);
2835         XtReleaseGC(shellWidget, jailSquareGC);
2836         XtReleaseGC(shellWidget, wdPieceGC);
2837         XtReleaseGC(shellWidget, wlPieceGC);
2838         XtReleaseGC(shellWidget, wjPieceGC);
2839         XtReleaseGC(shellWidget, bdPieceGC);
2840         XtReleaseGC(shellWidget, blPieceGC);
2841         XtReleaseGC(shellWidget, bjPieceGC);
2842     }
2843 }
2844
2845 void
2846 CreateGCs (int redo)
2847 {
2848     XtGCMask value_mask = GCLineWidth | GCLineStyle | GCForeground
2849       | GCBackground | GCFunction | GCPlaneMask;
2850     XGCValues gc_values;
2851     GC copyInvertedGC;
2852
2853     gc_values.plane_mask = AllPlanes;
2854     gc_values.line_width = lineGap;
2855     gc_values.line_style = LineSolid;
2856     gc_values.function = GXcopy;
2857
2858   if(redo) {
2859     DeleteGCs(); // called a second time; clean up old GCs first
2860   } else { // [HGM] grid and font GCs created on first call only
2861     gc_values.foreground = XBlackPixel(xDisplay, xScreen);
2862     gc_values.background = XWhitePixel(xDisplay, xScreen);
2863     coordGC = XtGetGC(shellWidget, value_mask, &gc_values);
2864     XSetFont(xDisplay, coordGC, coordFontID);
2865
2866     // [HGM] make font for holdings counts (white on black)
2867     gc_values.foreground = XWhitePixel(xDisplay, xScreen);
2868     gc_values.background = XBlackPixel(xDisplay, xScreen);
2869     countGC = XtGetGC(shellWidget, value_mask, &gc_values);
2870     XSetFont(xDisplay, countGC, countFontID);
2871   }
2872     gc_values.foreground = XBlackPixel(xDisplay, xScreen);
2873     gc_values.background = XBlackPixel(xDisplay, xScreen);
2874     lineGC = XtGetGC(shellWidget, value_mask, &gc_values);
2875
2876     if (appData.monoMode) {
2877         gc_values.foreground = XWhitePixel(xDisplay, xScreen);
2878         gc_values.background = XWhitePixel(xDisplay, xScreen);
2879         highlineGC = XtGetGC(shellWidget, value_mask, &gc_values);
2880
2881         gc_values.foreground = XWhitePixel(xDisplay, xScreen);
2882         gc_values.background = XBlackPixel(xDisplay, xScreen);
2883         lightSquareGC = wbPieceGC
2884           = XtGetGC(shellWidget, value_mask, &gc_values);
2885
2886         gc_values.foreground = XBlackPixel(xDisplay, xScreen);
2887         gc_values.background = XWhitePixel(xDisplay, xScreen);
2888         darkSquareGC = bwPieceGC
2889           = XtGetGC(shellWidget, value_mask, &gc_values);
2890
2891         if (DefaultDepth(xDisplay, xScreen) == 1) {
2892             /* Avoid XCopyPlane on 1-bit screens to work around Sun bug */
2893             gc_values.function = GXcopyInverted;
2894             copyInvertedGC = XtGetGC(shellWidget, value_mask, &gc_values);
2895             gc_values.function = GXcopy;
2896             if (XBlackPixel(xDisplay, xScreen) == 1) {
2897                 bwPieceGC = darkSquareGC;
2898                 wbPieceGC = copyInvertedGC;
2899             } else {
2900                 bwPieceGC = copyInvertedGC;
2901                 wbPieceGC = lightSquareGC;
2902             }
2903         }
2904     } else {
2905         gc_values.foreground = highlightSquareColor;
2906         gc_values.background = highlightSquareColor;
2907         highlineGC = XtGetGC(shellWidget, value_mask, &gc_values);
2908
2909         gc_values.foreground = premoveHighlightColor;
2910         gc_values.background = premoveHighlightColor;
2911         prelineGC = XtGetGC(shellWidget, value_mask, &gc_values);
2912
2913         gc_values.foreground = lightSquareColor;
2914         gc_values.background = darkSquareColor;
2915         lightSquareGC = XtGetGC(shellWidget, value_mask, &gc_values);
2916
2917         gc_values.foreground = darkSquareColor;
2918         gc_values.background = lightSquareColor;
2919         darkSquareGC = XtGetGC(shellWidget, value_mask, &gc_values);
2920
2921         gc_values.foreground = jailSquareColor;
2922         gc_values.background = jailSquareColor;
2923         jailSquareGC = XtGetGC(shellWidget, value_mask, &gc_values);
2924
2925         gc_values.foreground = whitePieceColor;
2926         gc_values.background = darkSquareColor;
2927         wdPieceGC = XtGetGC(shellWidget, value_mask, &gc_values);
2928
2929         gc_values.foreground = whitePieceColor;
2930         gc_values.background = lightSquareColor;
2931         wlPieceGC = XtGetGC(shellWidget, value_mask, &gc_values);
2932
2933         gc_values.foreground = whitePieceColor;
2934         gc_values.background = jailSquareColor;
2935         wjPieceGC = XtGetGC(shellWidget, value_mask, &gc_values);
2936
2937         gc_values.foreground = blackPieceColor;
2938         gc_values.background = darkSquareColor;
2939         bdPieceGC = XtGetGC(shellWidget, value_mask, &gc_values);
2940
2941         gc_values.foreground = blackPieceColor;
2942         gc_values.background = lightSquareColor;
2943         blPieceGC = XtGetGC(shellWidget, value_mask, &gc_values);
2944
2945         gc_values.foreground = blackPieceColor;
2946         gc_values.background = jailSquareColor;
2947         bjPieceGC = XtGetGC(shellWidget, value_mask, &gc_values);
2948     }
2949 }
2950
2951 void
2952 loadXIM (XImage *xim, XImage *xmask, char *filename, Pixmap *dest, Pixmap *mask)
2953 {
2954     int x, y, w, h, p;
2955     FILE *fp;
2956     Pixmap temp;
2957     XGCValues   values;
2958     GC maskGC;
2959
2960     fp = fopen(filename, "rb");
2961     if (!fp) {
2962         fprintf(stderr, _("%s: error loading XIM!\n"), programName);
2963         exit(1);
2964     }
2965
2966     w = fgetc(fp);
2967     h = fgetc(fp);
2968
2969     for (y=0; y<h; ++y) {
2970         for (x=0; x<h; ++x) {
2971             p = fgetc(fp);
2972
2973             switch (p) {
2974               case 0:
2975                 XPutPixel(xim, x, y, blackPieceColor);
2976                 if (xmask)
2977                   XPutPixel(xmask, x, y, WhitePixel(xDisplay,xScreen));
2978                 break;
2979               case 1:
2980                 XPutPixel(xim, x, y, darkSquareColor);
2981                 if (xmask)
2982                   XPutPixel(xmask, x, y, BlackPixel(xDisplay,xScreen));
2983                 break;
2984               case 2:
2985                 XPutPixel(xim, x, y, whitePieceColor);
2986                 if (xmask)
2987                   XPutPixel(xmask, x, y, WhitePixel(xDisplay,xScreen));
2988                 break;
2989               case 3:
2990                 XPutPixel(xim, x, y, lightSquareColor);
2991                 if (xmask)
2992                   XPutPixel(xmask, x, y, BlackPixel(xDisplay,xScreen));
2993                 break;
2994             }
2995         }
2996     }
2997
2998     fclose(fp);
2999
3000     /* create Pixmap of piece */
3001     *dest = XCreatePixmap(xDisplay, DefaultRootWindow(xDisplay),
3002                           w, h, xim->depth);
3003     XPutImage(xDisplay, *dest, lightSquareGC, xim,
3004               0, 0, 0, 0, w, h);
3005
3006     /* create Pixmap of clipmask
3007        Note: We assume the white/black pieces have the same
3008              outline, so we make only 6 masks. This is okay
3009              since the XPM clipmask routines do the same. */
3010     if (xmask) {
3011       temp = XCreatePixmap(xDisplay, DefaultRootWindow(xDisplay),
3012                             w, h, xim->depth);
3013       XPutImage(xDisplay, temp, lightSquareGC, xmask,
3014               0, 0, 0, 0, w, h);
3015
3016       /* now create the 1-bit version */
3017       *mask = XCreatePixmap(xDisplay, DefaultRootWindow(xDisplay),
3018                           w, h, 1);
3019
3020       values.foreground = 1;
3021       values.background = 0;
3022
3023       /* Don't use XtGetGC, not read only */
3024       maskGC = XCreateGC(xDisplay, *mask,
3025                     GCForeground | GCBackground, &values);
3026       XCopyPlane(xDisplay, temp, *mask, maskGC,
3027                   0, 0, squareSize, squareSize, 0, 0, 1);
3028       XFreePixmap(xDisplay, temp);
3029     }
3030 }
3031
3032
3033 char pieceBitmapNames[] = "pnbrqfeacwmohijgdvlsukpnsl";
3034
3035 void
3036 CreateXIMPieces ()
3037 {
3038     int piece, kind;
3039     char buf[MSG_SIZ];
3040     u_int ss;
3041     static char *ximkind[] = { "ll", "ld", "dl", "dd" };
3042     XImage *ximtemp;
3043
3044     ss = squareSize;
3045
3046     /* The XSynchronize calls were copied from CreatePieces.
3047        Not sure if needed, but can't hurt */
3048     XSynchronize(xDisplay, True); /* Work-around for xlib/xt
3049                                      buffering bug */
3050
3051     /* temp needed by loadXIM() */
3052     ximtemp = XGetImage(xDisplay, DefaultRootWindow(xDisplay),
3053                  0, 0, ss, ss, AllPlanes, XYPixmap);
3054
3055     if (strlen(appData.pixmapDirectory) == 0) {
3056       useImages = 0;
3057     } else {
3058         useImages = 1;
3059         if (appData.monoMode) {
3060           DisplayFatalError(_("XIM pieces cannot be used in monochrome mode"),
3061                             0, 2);
3062           ExitEvent(2);
3063         }
3064         fprintf(stderr, _("\nLoading XIMs...\n"));
3065         /* Load pieces */
3066         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
3067             fprintf(stderr, "%d", piece+1);
3068             for (kind=0; kind<4; kind++) {
3069                 fprintf(stderr, ".");
3070                 snprintf(buf, sizeof(buf), "%s/%s%c%s%u.xim",
3071                         ExpandPathName(appData.pixmapDirectory),
3072                         piece <= (int) WhiteKing ? "" : "w",
3073                         pieceBitmapNames[piece],
3074                         ximkind[kind], ss);
3075                 ximPieceBitmap[kind][piece] =
3076                   XGetImage(xDisplay, DefaultRootWindow(xDisplay),
3077                             0, 0, ss, ss, AllPlanes, XYPixmap);
3078                 if (appData.debugMode)
3079                   fprintf(stderr, _("(File:%s:) "), buf);
3080                 loadXIM(ximPieceBitmap[kind][piece],
3081                         ximtemp, buf,
3082                         &(xpmPieceBitmap2[kind][piece]),
3083                         &(ximMaskPm2[piece]));
3084                 if(piece <= (int)WhiteKing)
3085                     xpmPieceBitmap[kind][piece] = xpmPieceBitmap2[kind][piece];
3086             }
3087             fprintf(stderr," ");
3088         }
3089         /* Load light and dark squares */
3090         /* If the LSQ and DSQ pieces don't exist, we will
3091            draw them with solid squares. */
3092         snprintf(buf,sizeof(buf), "%s/lsq%u.xim", ExpandPathName(appData.pixmapDirectory), ss);
3093         if (access(buf, 0) != 0) {
3094             useImageSqs = 0;
3095         } else {
3096             useImageSqs = 1;
3097             fprintf(stderr, _("light square "));
3098             ximLightSquare=
3099               XGetImage(xDisplay, DefaultRootWindow(xDisplay),
3100                         0, 0, ss, ss, AllPlanes, XYPixmap);
3101             if (appData.debugMode)
3102               fprintf(stderr, _("(File:%s:) "), buf);
3103
3104             loadXIM(ximLightSquare, NULL, buf, &xpmLightSquare, NULL);
3105             fprintf(stderr, _("dark square "));
3106             snprintf(buf,sizeof(buf), "%s/dsq%u.xim",
3107                     ExpandPathName(appData.pixmapDirectory), ss);
3108             if (appData.debugMode)
3109               fprintf(stderr, _("(File:%s:) "), buf);
3110             ximDarkSquare=
3111               XGetImage(xDisplay, DefaultRootWindow(xDisplay),
3112                         0, 0, ss, ss, AllPlanes, XYPixmap);
3113             loadXIM(ximDarkSquare, NULL, buf, &xpmDarkSquare, NULL);
3114             xpmJailSquare = xpmLightSquare;
3115         }
3116         fprintf(stderr, _("Done.\n"));
3117     }
3118     XSynchronize(xDisplay, False); /* Work-around for xlib/xt buffering bug */
3119 }
3120
3121 static VariantClass oldVariant = (VariantClass) -1; // [HGM] pieces: redo every time variant changes
3122
3123 #if HAVE_LIBXPM
3124 void
3125 CreateXPMBoard (char *s, int kind)
3126 {
3127     XpmAttributes attr;
3128     attr.valuemask = 0;
3129     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
3130     if (XpmReadFileToPixmap(xDisplay, xBoardWindow, s, &(xpmBoardBitmap[kind]), NULL, &attr) == 0) {
3131         useTexture |= kind + 1; textureW[kind] = attr.width; textureH[kind] = attr.height;
3132     }
3133 }
3134
3135 void
3136 FreeXPMPieces ()
3137 {   // [HGM] to prevent resoucre leak on calling CreaeXPMPieces() a second time,
3138     // thisroutine has to be called t free the old piece pixmaps
3139     int piece, kind;
3140     for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++)
3141         for (kind=0; kind<4; kind++) XFreePixmap(xDisplay, xpmPieceBitmap2[kind][piece]);
3142     if(useImageSqs) {
3143         XFreePixmap(xDisplay, xpmLightSquare);
3144         XFreePixmap(xDisplay, xpmDarkSquare);
3145     }
3146 }
3147
3148 void
3149 CreateXPMPieces ()
3150 {
3151     int piece, kind, r;
3152     char buf[MSG_SIZ];
3153     u_int ss = squareSize;
3154     XpmAttributes attr;
3155     static char *xpmkind[] = { "ll", "ld", "dl", "dd" };
3156     XpmColorSymbol symbols[4];
3157     static int redo = False;
3158
3159     if(redo) FreeXPMPieces(); else redo = 1;
3160
3161     /* The XSynchronize calls were copied from CreatePieces.
3162        Not sure if needed, but can't hurt */
3163     XSynchronize(xDisplay, True); /* Work-around for xlib/xt buffering bug */
3164
3165     /* Setup translations so piece colors match square colors */
3166     symbols[0].name = "light_piece";
3167     symbols[0].value = appData.whitePieceColor;
3168     symbols[1].name = "dark_piece";
3169     symbols[1].value = appData.blackPieceColor;
3170     symbols[2].name = "light_square";
3171     symbols[2].value = appData.lightSquareColor;
3172     symbols[3].name = "dark_square";
3173     symbols[3].value = appData.darkSquareColor;
3174
3175     attr.valuemask = XpmColorSymbols;
3176     attr.colorsymbols = symbols;
3177     attr.numsymbols = 4;
3178
3179     if (appData.monoMode) {
3180       DisplayFatalError(_("XPM pieces cannot be used in monochrome mode"),
3181                         0, 2);
3182       ExitEvent(2);
3183     }
3184     if (strlen(appData.pixmapDirectory) == 0) {
3185         XpmPieces* pieces = builtInXpms;
3186         useImages = 1;
3187         /* Load pieces */
3188         while (pieces->size != squareSize && pieces->size) pieces++;
3189         if (!pieces->size) {
3190           fprintf(stderr, _("No builtin XPM pieces of size %d\n"), squareSize);
3191           exit(1);
3192         }
3193         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
3194             for (kind=0; kind<4; kind++) {
3195
3196                 if ((r=XpmCreatePixmapFromData(xDisplay, xBoardWindow,
3197                                                pieces->xpm[piece][kind],
3198                                                &(xpmPieceBitmap2[kind][piece]),
3199                                                NULL, &attr)) != 0) {
3200                   fprintf(stderr, _("Error %d loading XPM image \"%s\"\n"),
3201                           r, buf);
3202                   exit(1);
3203                 }
3204                 if(piece <= (int) WhiteKing)
3205                     xpmPieceBitmap[kind][piece] = xpmPieceBitmap2[kind][piece];
3206             }
3207         }
3208         useImageSqs = 0;
3209         xpmJailSquare = xpmLightSquare;
3210     } else {
3211         useImages = 1;
3212
3213         fprintf(stderr, _("\nLoading XPMs...\n"));
3214
3215         /* Load pieces */
3216         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
3217             fprintf(stderr, "%d ", piece+1);
3218             for (kind=0; kind<4; kind++) {
3219               snprintf(buf, sizeof(buf), "%s/%s%c%s%u.xpm",
3220                         ExpandPathName(appData.pixmapDirectory),
3221                         piece > (int) WhiteKing ? "w" : "",
3222                         pieceBitmapNames[piece],
3223                         xpmkind[kind], ss);
3224                 if (appData.debugMode) {
3225                     fprintf(stderr, _("(File:%s:) "), buf);
3226                 }
3227                 if ((r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
3228                                            &(xpmPieceBitmap2[kind][piece]),
3229                                            NULL, &attr)) != 0) {
3230                     if(piece != (int)WhiteKing && piece > (int)WhiteQueen) {
3231                       // [HGM] missing: read of unorthodox piece failed; substitute King.
3232                       snprintf(buf, sizeof(buf), "%s/k%s%u.xpm",
3233                                 ExpandPathName(appData.pixmapDirectory),
3234                                 xpmkind[kind], ss);
3235                         if (appData.debugMode) {
3236                             fprintf(stderr, _("(Replace by File:%s:) "), buf);
3237                         }
3238                         r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
3239                                                 &(xpmPieceBitmap2[kind][piece]),
3240                                                 NULL, &attr);
3241                     }
3242                     if (r != 0) {
3243                         fprintf(stderr, _("Error %d loading XPM file \"%s\"\n"),
3244                                 r, buf);
3245                         exit(1);
3246                     }
3247                 }
3248                 if(piece <= (int) WhiteKing)
3249                     xpmPieceBitmap[kind][piece] = xpmPieceBitmap2[kind][piece];
3250             }
3251         }
3252         /* Load light and dark squares */
3253         /* If the LSQ and DSQ pieces don't exist, we will
3254            draw them with solid squares. */
3255         fprintf(stderr, _("light square "));
3256         snprintf(buf, sizeof(buf), "%s/lsq%u.xpm", ExpandPathName(appData.pixmapDirectory), ss);
3257         if (access(buf, 0) != 0) {
3258             useImageSqs = 0;
3259         } else {
3260             useImageSqs = 1;
3261             if (appData.debugMode)
3262               fprintf(stderr, _("(File:%s:) "), buf);
3263
3264             if ((r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
3265                                        &xpmLightSquare, NULL, &attr)) != 0) {
3266                 fprintf(stderr, _("Error %d loading XPM file \"%s\"\n"), r, buf);
3267                 exit(1);
3268             }
3269             fprintf(stderr, _("dark square "));
3270             snprintf(buf, sizeof(buf), "%s/dsq%u.xpm",
3271                     ExpandPathName(appData.pixmapDirectory), ss);
3272             if (appData.debugMode) {
3273                 fprintf(stderr, _("(File:%s:) "), buf);
3274             }
3275             if ((r=XpmReadFileToPixmap(xDisplay, xBoardWindow, buf,
3276                                        &xpmDarkSquare, NULL, &attr)) != 0) {
3277                 fprintf(stderr, _("Error %d loading XPM file \"%s\"\n"), r, buf);
3278                 exit(1);
3279             }
3280         }
3281         xpmJailSquare = xpmLightSquare;
3282         fprintf(stderr, _("Done.\n"));
3283     }
3284     oldVariant = -1; // kludge to force re-makig of animation masks
3285     XSynchronize(xDisplay, False); /* Work-around for xlib/xt
3286                                       buffering bug */
3287 }
3288 #endif /* HAVE_LIBXPM */
3289
3290 #if HAVE_LIBXPM
3291 /* No built-in bitmaps */
3292 void CreatePieces()
3293 {
3294     int piece, kind;
3295     char buf[MSG_SIZ];
3296     u_int ss = squareSize;
3297
3298     XSynchronize(xDisplay, True); /* Work-around for xlib/xt
3299                                      buffering bug */
3300
3301     for (kind = SOLID; kind <= (appData.monoMode ? OUTLINE : SOLID); kind++) {
3302         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
3303           snprintf(buf, MSG_SIZ, "%s%c%u%c.bm", piece > (int)WhiteKing ? "w" : "",
3304                    pieceBitmapNames[piece],
3305                    ss, kind == SOLID ? 's' : 'o');
3306           ReadBitmap(&pieceBitmap2[kind][piece], buf, NULL, ss, ss);
3307           if(piece <= (int)WhiteKing)
3308             pieceBitmap[kind][piece] = pieceBitmap2[kind][piece];
3309         }
3310     }
3311
3312     XSynchronize(xDisplay, False); /* Work-around for xlib/xt
3313                                       buffering bug */
3314 }
3315 #else
3316 /* With built-in bitmaps */
3317 void
3318 CreatePieces ()
3319 {
3320     BuiltInBits* bib = builtInBits;
3321     int piece, kind;
3322     char buf[MSG_SIZ];
3323     u_int ss = squareSize;
3324
3325     XSynchronize(xDisplay, True); /* Work-around for xlib/xt
3326                                      buffering bug */
3327
3328     while (bib->squareSize != ss && bib->squareSize != 0) bib++;
3329
3330     for (kind = SOLID; kind <= (appData.monoMode ? OUTLINE : SOLID); kind++) {
3331         for (piece = (int) WhitePawn; piece <= (int) WhiteKing + 4; piece++) {
3332           snprintf(buf, MSG_SIZ, "%s%c%u%c.bm", piece > (int)WhiteKing ? "w" : "",
3333                    pieceBitmapNames[piece],
3334                    ss, kind == SOLID ? 's' : 'o');
3335           ReadBitmap(&pieceBitmap2[kind][piece], buf,
3336                      bib->bits[kind][piece], ss, ss);
3337           if(piece <= (int)WhiteKing)
3338             pieceBitmap[kind][piece] = pieceBitmap2[kind][piece];
3339         }
3340     }
3341
3342     XSynchronize(xDisplay, False); /* Work-around for xlib/xt
3343                                       buffering bug */
3344 }
3345 #endif
3346
3347 void
3348 ReadBitmap (Pixmap *pm, String name, unsigned char bits[], u_int wreq, u_int hreq)
3349 {
3350     int x_hot, y_hot;
3351     u_int w, h;
3352     int errcode;
3353     char msg[MSG_SIZ], fullname[MSG_SIZ];
3354
3355     if (*appData.bitmapDirectory != NULLCHAR) {
3356       safeStrCpy(fullname, appData.bitmapDirectory, sizeof(fullname)/sizeof(fullname[0]) );
3357       strncat(fullname, "/", MSG_SIZ - strlen(fullname) - 1);
3358       strncat(fullname, name, MSG_SIZ - strlen(fullname) - 1);
3359       errcode = XReadBitmapFile(xDisplay, xBoardWindow, fullname,
3360                                 &w, &h, pm, &x_hot, &y_hot);
3361       fprintf(stderr, "load %s\n", name);
3362         if (errcode != BitmapSuccess) {
3363             switch (errcode) {
3364               case BitmapOpenFailed:
3365                 snprintf(msg, sizeof(msg), _("Can't open bitmap file %s"), fullname);
3366                 break;
3367               case BitmapFileInvalid:
3368                 snprintf(msg, sizeof(msg), _("Invalid bitmap in file %s"), fullname);
3369                 break;
3370               case BitmapNoMemory:
3371                 snprintf(msg, sizeof(msg), _("Ran out of memory reading bitmap file %s"),
3372                         fullname);
3373                 break;
3374               default:
3375                 snprintf(msg, sizeof(msg), _("Unknown XReadBitmapFile error %d on file %s"),
3376                         errcode, fullname);
3377                 break;
3378             }
3379             fprintf(stderr, _("%s: %s...using built-in\n"),
3380                     programName, msg);
3381         } else if (w != wreq || h != hreq) {
3382             fprintf(stderr,
3383                     _("%s: Bitmap %s is %dx%d, not %dx%d...using built-in\n"),
3384                     programName, fullname, w, h, wreq, hreq);
3385         } else {
3386             return;
3387         }
3388     }
3389     if (bits != NULL) {
3390         *pm = XCreateBitmapFromData(xDisplay, xBoardWindow, (char *) bits,
3391                                     wreq, hreq);
3392     }
3393 }
3394
3395 void
3396 CreateGrid ()
3397 {
3398     int i, j;
3399
3400     if (lineGap == 0) return;
3401
3402     /* [HR] Split this into 2 loops for non-square boards. */
3403
3404     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
3405         gridSegments[i].x1 = 0;
3406         gridSegments[i].x2 =
3407           lineGap + BOARD_WIDTH * (squareSize + lineGap);
3408         gridSegments[i].y1 = gridSegments[i].y2
3409           = lineGap / 2 + (i * (squareSize + lineGap));
3410     }
3411
3412     for (j = 0; j < BOARD_WIDTH + 1; j++) {
3413         gridSegments[j + i].y1 = 0;
3414         gridSegments[j + i].y2 =
3415           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
3416         gridSegments[j + i].x1 = gridSegments[j + i].x2
3417           = lineGap / 2 + (j * (squareSize + lineGap));
3418     }
3419 }
3420
3421 int nrOfMenuItems = 7;
3422 Widget menuWidget[150];
3423 MenuListItem menuItemList[150] = {
3424     { "LoadNextGameProc", LoadNextGameProc },
3425     { "LoadPrevGameProc", LoadPrevGameProc },
3426     { "ReloadGameProc", ReloadGameProc },
3427     { "ReloadPositionProc", ReloadPositionProc },
3428 #ifndef OPTIONSDIALOG
3429     { "AlwaysQueenProc", AlwaysQueenProc },
3430     { "AnimateDraggingProc", AnimateDraggingProc },
3431     { "AnimateMovingProc", AnimateMovingProc },
3432     { "AutoflagProc", AutoflagProc },
3433     { "AutoflipProc", AutoflipProc },
3434     { "BlindfoldProc", BlindfoldProc },
3435     { "FlashMovesProc", FlashMovesProc },
3436 #if HIGHDRAG
3437     { "HighlightDraggingProc", HighlightDraggingProc },
3438 #endif
3439     { "HighlightLastMoveProc", HighlightLastMoveProc },
3440 //    { "IcsAlarmProc", IcsAlarmProc },
3441     { "MoveSoundProc", MoveSoundProc },
3442     { "PeriodicUpdatesProc", PeriodicUpdatesProc },
3443     { "PopupExitMessageProc", PopupExitMessageProc },
3444     { "PopupMoveErrorsProc", PopupMoveErrorsProc },
3445 //    { "PremoveProc", PremoveProc },
3446     { "ShowCoordsProc", ShowCoordsProc },
3447     { "ShowThinkingProc", ShowThinkingProc },
3448     { "HideThinkingProc", HideThinkingProc },
3449     { "TestLegalityProc", TestLegalityProc },
3450 #endif
3451     { "AboutGameProc", AboutGameEvent },
3452     { "DebugProc", DebugProc },
3453     { "NothingProc", NothingProc },
3454   {NULL, NothingProc}
3455 };
3456
3457 void
3458 MarkMenuItem (char *menuRef, int state)
3459 {
3460     int nr = MenuToNumber(menuRef);
3461     if(nr >= 0) {
3462         Arg args[2];
3463         XtSetArg(args[0], XtNleftBitmap, state ? xMarkPixmap : None);
3464         XtSetValues(menuWidget[nr], args, 1);
3465     }
3466 }
3467
3468 void
3469 EnableMenuItem (char *menuRef, int state)
3470 {
3471     int nr = MenuToNumber(menuRef);
3472     if(nr >= 0) XtSetSensitive(menuWidget[nr], state);
3473 }
3474
3475 void
3476 SetMenuEnables (Enables *enab)
3477 {
3478   while (enab->name != NULL) {
3479     EnableMenuItem(enab->name, enab->value);
3480     enab++;
3481   }
3482 }
3483
3484 int
3485 Equal(char *p, char *s)
3486 {   // compare strings skipping spaces in second
3487     while(*s) {
3488         if(*s == ' ') { s++; continue; }
3489         if(*s++ != *p++) return 0;
3490     }
3491     return !*p;
3492 }
3493
3494 void
3495 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3496 {   // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
3497     int i;
3498     if(*nprms == 0) return;
3499     for(i=0; menuItemList[i].name; i++) {
3500         if(Equal(prms[0], menuItemList[i].name)) {
3501             (menuItemList[i].proc) ();
3502             return;
3503         }
3504     }
3505 }
3506
3507 static void
3508 MenuBarSelect (Widget w, caddr_t addr, caddr_t index)
3509 {
3510     MenuProc *proc = (MenuProc *) addr;
3511
3512     (proc)();
3513 }
3514
3515 static void
3516 MenuEngineSelect (Widget w, caddr_t addr, caddr_t index)
3517 {
3518     RecentEngineEvent((int) (intptr_t) addr);
3519 }
3520
3521 // some stuff that must remain in front-end
3522 static Widget mainBar, currentMenu;
3523 static int wtot, nr = 0, widths[10];
3524
3525 void
3526 AppendMenuItem (char *text, char *name, MenuProc *action)
3527 {
3528     int j;
3529     Widget entry;
3530     Arg args[16];
3531
3532     j = 0;
3533     XtSetArg(args[j], XtNleftMargin, 20);   j++;
3534     XtSetArg(args[j], XtNrightMargin, 20);  j++;
3535
3536         if (strcmp(text, "----") == 0) {
3537           entry = XtCreateManagedWidget(text, smeLineObjectClass,
3538                                           currentMenu, args, j);
3539         } else {
3540           XtSetArg(args[j], XtNlabel, XtNewString(_(text)));
3541             entry = XtCreateManagedWidget(name, smeBSBObjectClass,
3542                                           currentMenu, args, j+1);
3543             XtAddCallback(entry, XtNcallback,
3544                           (XtCallbackProc) (strcmp(name, "recent") ? MenuBarSelect : MenuEngineSelect),
3545                           (caddr_t) action);
3546             menuWidget[nrOfMenuItems] = entry;
3547         }
3548 }
3549
3550 void
3551 CreateMenuButton (char *name, Menu *mb)
3552 {   // create menu button on main bar, and shell for pull-down list
3553     int i, j;
3554     Arg args[16];
3555     Dimension w;
3556
3557         j = 0;
3558         XtSetArg(args[j], XtNmenuName, XtNewString(name));  j++;
3559         XtSetArg(args[j], XtNlabel, XtNewString(_(mb->name)));  j++;
3560         XtSetArg(args[j], XtNborderWidth, 0);                   j++;
3561         mb->subMenu = XtCreateManagedWidget(mb->name, menuButtonWidgetClass,
3562                                        mainBar, args, j);
3563     currentMenu = XtCreatePopupShell(name, simpleMenuWidgetClass,
3564                               mainBar, NULL, 0);
3565         j = 0;
3566         XtSetArg(args[j], XtNwidth, &w);                   j++;
3567         XtGetValues(mb->subMenu, args, j);
3568         wtot += mb->textWidth = widths[nr++] = w;
3569 }
3570
3571 Widget
3572 CreateMenuBar (Menu *mb, int boardWidth)
3573 {
3574     int i, j;
3575     Arg args[16];
3576     char menuName[MSG_SIZ];
3577     Dimension w;
3578     Menu *ma = mb;
3579
3580     // create bar itself
3581     j = 0;
3582     XtSetArg(args[j], XtNorientation, XtorientHorizontal);  j++;
3583     XtSetArg(args[j], XtNvSpace, 0);                        j++;
3584     XtSetArg(args[j], XtNborderWidth, 0);                   j++;
3585     mainBar = XtCreateWidget("menuBar", boxWidgetClass,
3586                              formWidget, args, j);
3587
3588     CreateMainMenus(mb); // put menus in bar according to description in back-end
3589
3590     // size buttons to make menu bar fit, clipping menu names where necessary
3591     while(wtot > boardWidth - 40) {
3592         int wmax=0, imax=0;
3593         for(i=0; i<nr; i++) if(widths[i] > wmax) wmax = widths[imax=i];
3594         widths[imax]--;
3595         wtot--;
3596     }
3597     for(i=0; i<nr; i++) if(widths[i] != ma[i].textWidth) {
3598         j = 0;
3599         XtSetArg(args[j], XtNwidth, widths[i]);                   j++;
3600         XtSetValues(ma[i].subMenu, args, j);
3601     }
3602
3603     return mainBar;
3604 }
3605
3606 Widget
3607 CreateButtonBar (MenuItem *mi)
3608 {
3609     int j;
3610     Widget button, buttonBar;
3611     Arg args[16];
3612
3613     j = 0;
3614     XtSetArg(args[j], XtNorientation, XtorientHorizontal); j++;
3615     if (tinyLayout) {
3616         XtSetArg(args[j], XtNhSpace, 0); j++;
3617     }
3618     XtSetArg(args[j], XtNborderWidth, 0); j++;
3619     XtSetArg(args[j], XtNvSpace, 0);                        j++;
3620     buttonBar = XtCreateWidget("buttonBar", boxWidgetClass,
3621                                formWidget, args, j);
3622
3623     while (mi->string != NULL) {
3624         j = 0;
3625         if (tinyLayout) {
3626             XtSetArg(args[j], XtNinternalWidth, 2); j++;
3627             XtSetArg(args[j], XtNborderWidth, 0); j++;
3628         }
3629       XtSetArg(args[j], XtNlabel, XtNewString(_(mi->string))); j++;
3630         button = XtCreateManagedWidget(mi->string, commandWidgetClass,
3631                                        buttonBar, args, j);
3632         XtAddCallback(button, XtNcallback,
3633                       (XtCallbackProc) MenuBarSelect,
3634                       (caddr_t) mi->proc);
3635         mi++;
3636     }
3637     return buttonBar;
3638 }
3639
3640 Widget
3641 CreatePieceMenu (char *name, int color)
3642 {
3643     int i;
3644     Widget entry, menu;
3645     Arg args[16];
3646     ChessSquare selection;
3647
3648     menu = XtCreatePopupShell(name, simpleMenuWidgetClass,
3649                               boardWidget, args, 0);
3650
3651     for (i = 0; i < PIECE_MENU_SIZE; i++) {
3652         String item = pieceMenuStrings[color][i];
3653
3654         if (strcmp(item, "----") == 0) {
3655             entry = XtCreateManagedWidget(item, smeLineObjectClass,
3656                                           menu, NULL, 0);
3657         } else {
3658           XtSetArg(args[0], XtNlabel, XtNewString(_(item)));
3659             entry = XtCreateManagedWidget(item, smeBSBObjectClass,
3660                                 menu, args, 1);
3661             selection = pieceMenuTranslation[color][i];
3662             XtAddCallback(entry, XtNcallback,
3663                           (XtCallbackProc) PieceMenuSelect,
3664                           (caddr_t) selection);
3665             if (selection == WhitePawn || selection == BlackPawn) {
3666                 XtSetArg(args[0], XtNpopupOnEntry, entry);
3667                 XtSetValues(menu, args, 1);
3668             }
3669         }
3670     }
3671     return menu;
3672 }
3673
3674 void
3675 CreatePieceMenus ()
3676 {
3677     int i;
3678     Widget entry;
3679     Arg args[16];
3680     ChessSquare selection;
3681
3682     whitePieceMenu = CreatePieceMenu("menuW", 0);
3683     blackPieceMenu = CreatePieceMenu("menuB", 1);
3684
3685     if(appData.pieceMenu) // [HGM] sweep: no idea what this was good for, but it stopped reporting button events outside the window
3686     XtRegisterGrabAction(PieceMenuPopup, True,
3687                          (unsigned)(ButtonPressMask|ButtonReleaseMask),
3688                          GrabModeAsync, GrabModeAsync);
3689
3690     XtSetArg(args[0], XtNlabel, _("Drop"));
3691     dropMenu = XtCreatePopupShell("menuD", simpleMenuWidgetClass,
3692                                   boardWidget, args, 1);
3693     for (i = 0; i < DROP_MENU_SIZE; i++) {
3694         String item = dropMenuStrings[i];
3695
3696         if (strcmp(item, "----") == 0) {
3697             entry = XtCreateManagedWidget(item, smeLineObjectClass,
3698                                           dropMenu, NULL, 0);
3699         } else {
3700           XtSetArg(args[0], XtNlabel, XtNewString(_(item)));
3701             entry = XtCreateManagedWidget(item, smeBSBObjectClass,
3702                                 dropMenu, args, 1);
3703             selection = dropMenuTranslation[i];
3704             XtAddCallback(entry, XtNcallback,
3705                           (XtCallbackProc) DropMenuSelect,
3706                           (caddr_t) selection);
3707         }
3708     }
3709 }
3710
3711 void
3712 SetupDropMenu ()
3713 {
3714     int i, j, count;
3715     char label[32];
3716     Arg args[16];
3717     Widget entry;
3718     char* p;
3719
3720     for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
3721         entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
3722         p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
3723                    dmEnables[i].piece);
3724         XtSetSensitive(entry, p != NULL || !appData.testLegality
3725                        /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
3726                                        && !appData.icsActive));
3727         count = 0;
3728         while (p && *p++ == dmEnables[i].piece) count++;
3729         snprintf(label, sizeof(label), "%s  %d", dmEnables[i].widget, count);
3730         j = 0;
3731         XtSetArg(args[j], XtNlabel, label); j++;
3732         XtSetValues(entry, args, j);
3733     }
3734 }
3735
3736 void
3737 PieceMenuPopup (Widget w, XEvent *event, String *params, Cardinal *num_params)
3738 {
3739     String whichMenu; int menuNr = -2;
3740     shiftKey = strcmp(params[0], "menuW"); // used to indicate black
3741     if (event->type == ButtonRelease)
3742         menuNr = RightClick(Release, event->xbutton.x, event->xbutton.y, &pmFromX, &pmFromY);
3743     else if (event->type == ButtonPress)
3744         menuNr = RightClick(Press,   event->xbutton.x, event->xbutton.y, &pmFromX, &pmFromY);
3745     switch(menuNr) {
3746       case 0: whichMenu = params[0]; break;
3747       case 1: SetupDropMenu(); whichMenu = "menuD"; break;
3748       case 2:
3749       case -1: if (errorUp) ErrorPopDown();
3750       default: return;
3751     }
3752     XtPopupSpringLoaded(XtNameToWidget(boardWidget, whichMenu));
3753 }
3754
3755 static void
3756 PieceMenuSelect (Widget w, ChessSquare piece, caddr_t junk)
3757 {
3758     if (pmFromX < 0 || pmFromY < 0) return;
3759     EditPositionMenuEvent(piece, pmFromX, pmFromY);
3760 }
3761
3762 static void
3763 DropMenuSelect (Widget w, ChessSquare piece, caddr_t junk)
3764 {
3765     if (pmFromX < 0 || pmFromY < 0) return;
3766     DropMenuEvent(piece, pmFromX, pmFromY);
3767 }
3768
3769 void
3770 WhiteClock (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3771 {
3772     shiftKey = prms[0][0] & 1;
3773     ClockClick(0);
3774 }
3775
3776 void
3777 BlackClock (Widget w, XEvent *event, String *prms, Cardinal *nprms)
3778 {
3779     shiftKey = prms[0][0] & 1;
3780     ClockClick(1);
3781 }
3782
3783
3784 /*
3785  * If the user selects on a border boundary, return -1; if off the board,
3786  *   return -2.  Otherwise map the event coordinate to the square.
3787  */
3788 int
3789 EventToSquare (int x, int limit)
3790 {
3791     if (x <= 0)
3792       return -2;
3793     if (x < lineGap)
3794       return -1;
3795     x -= lineGap;
3796     if ((x % (squareSize + lineGap)) >= squareSize)
3797       return -1;
3798     x /= (squareSize + lineGap);
3799     if (x >= limit)
3800       return -2;
3801     return x;
3802 }
3803
3804 static void
3805 do_flash_delay (unsigned long msec)
3806 {
3807     TimeDelay(msec);
3808 }
3809
3810 static void
3811 drawHighlight (int file, int rank, GC gc)
3812 {
3813     int x, y;
3814
3815     if (lineGap == 0) return;
3816
3817     if (flipView) {
3818         x = lineGap/2 + ((BOARD_WIDTH-1)-file) *
3819           (squareSize + lineGap);
3820         y = lineGap/2 + rank * (squareSize + lineGap);
3821     } else {
3822         x = lineGap/2 + file * (squareSize + lineGap);
3823         y = lineGap/2 + ((BOARD_HEIGHT-1)-rank) *
3824           (squareSize + lineGap);
3825     }
3826
3827     XDrawRectangle(xDisplay, xBoardWindow, gc, x, y,
3828                    squareSize+lineGap, squareSize+lineGap);
3829 }
3830
3831 int hi1X = -1, hi1Y = -1, hi2X = -1, hi2Y = -1;
3832 int pm1X = -1, pm1Y = -1, pm2X = -1, pm2Y = -1;
3833
3834 void
3835 SetHighlights (int fromX, int fromY, int toX, int toY)
3836 {
3837     if (hi1X != fromX || hi1Y != fromY) {
3838         if (hi1X >= 0 && hi1Y >= 0) {
3839             drawHighlight(hi1X, hi1Y, lineGC);
3840         }
3841     } // [HGM] first erase both, then draw new!
3842
3843     if (hi2X != toX || hi2Y != toY) {
3844         if (hi2X >= 0 && hi2Y >= 0) {
3845             drawHighlight(hi2X, hi2Y, lineGC);
3846         }
3847     }
3848     if (hi1X != fromX || hi1Y != fromY) {
3849         if (fromX >= 0 && fromY >= 0) {
3850             drawHighlight(fromX, fromY, highlineGC);
3851         }
3852     }
3853     if (hi2X != toX || hi2Y != toY) {
3854         if (toX >= 0 && toY >= 0) {
3855             drawHighlight(toX, toY, highlineGC);
3856         }
3857     }
3858
3859     if(toX<0) // clearing the highlights must have damaged arrow
3860         DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y); // for now, redraw it (should really be cleared!)
3861
3862     hi1X = fromX;
3863     hi1Y = fromY;
3864     hi2X = toX;
3865     hi2Y = toY;
3866 }
3867
3868 void
3869 ClearHighlights ()
3870 {
3871     SetHighlights(-1, -1, -1, -1);
3872 }
3873
3874
3875 void
3876 SetPremoveHighlights (int fromX, int fromY, int toX, int toY)
3877 {
3878     if (pm1X != fromX || pm1Y != fromY) {
3879         if (pm1X >= 0 && pm1Y >= 0) {
3880             drawHighlight(pm1X, pm1Y, lineGC);
3881         }
3882         if (fromX >= 0 && fromY >= 0) {
3883             drawHighlight(fromX, fromY, prelineGC);
3884         }
3885     }
3886     if (pm2X != toX || pm2Y != toY) {
3887         if (pm2X >= 0 && pm2Y >= 0) {
3888             drawHighlight(pm2X, pm2Y, lineGC);
3889         }
3890         if (toX >= 0 && toY >= 0) {
3891             drawHighlight(toX, toY, prelineGC);
3892         }
3893     }
3894     pm1X = fromX;
3895     pm1Y = fromY;
3896     pm2X = toX;
3897     pm2Y = toY;
3898 }
3899
3900 void
3901 ClearPremoveHighlights ()
3902 {
3903   SetPremoveHighlights(-1, -1, -1, -1);
3904 }
3905
3906 static int
3907 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
3908 {
3909     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
3910     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
3911     *x0 = 0; *y0 = 0;
3912     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
3913     if(textureW[kind] < W*squareSize)
3914         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
3915     else
3916         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
3917     if(textureH[kind] < H*squareSize)
3918         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
3919     else
3920         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
3921     return 1;
3922 }
3923
3924 static void
3925 BlankSquare (int x, int y, int color, ChessSquare piece, Drawable dest, int fac)
3926 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
3927     int x0, y0;
3928     if (useImages && color != 2 && (useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
3929         XCopyArea(xDisplay, xpmBoardBitmap[color], dest, wlPieceGC, x0, y0,
3930                   squareSize, squareSize, x*fac, y*fac);
3931     } else
3932     if (useImages && useImageSqs) {
3933         Pixmap pm;
3934         switch (color) {
3935           case 1: /* light */
3936             pm = xpmLightSquare;
3937             break;
3938           case 0: /* dark */
3939             pm = xpmDarkSquare;
3940             break;
3941           case 2: /* neutral */
3942           default:
3943             pm = xpmJailSquare;
3944             break;
3945         }
3946         XCopyArea(xDisplay, pm, dest, wlPieceGC, 0, 0,
3947                   squareSize, squareSize, x*fac, y*fac);
3948     } else {
3949         GC gc;
3950         switch (color) {
3951           case 1: /* light */
3952             gc = lightSquareGC;
3953             break;
3954           case 0: /* dark */
3955             gc = darkSquareGC;
3956             break;
3957           case 2: /* neutral */
3958           default:
3959             gc = jailSquareGC;
3960             break;
3961         }
3962         XFillRectangle(xDisplay, dest, gc, x*fac, y*fac, squareSize, squareSize);
3963     }
3964 }
3965
3966 /*
3967    I split out the routines to draw a piece so that I could
3968    make a generic flash routine.
3969 */
3970 static void
3971 monoDrawPiece_1bit (ChessSquare piece, int square_color, int x, int y, Drawable dest)
3972 {
3973     /* Avoid XCopyPlane on 1-bit screens to work around Sun bug */
3974     switch (square_color) {
3975       case 1: /* light */
3976       case 2: /* neutral */
3977       default:
3978         XCopyArea(xDisplay, (int) piece < (int) BlackPawn
3979                   ? *pieceToOutline(piece)
3980                   : *pieceToSolid(piece),
3981                   dest, bwPieceGC, 0, 0,
3982                   squareSize, squareSize, x, y);
3983         break;
3984       case 0: /* dark */
3985         XCopyArea(xDisplay, (int) piece < (int) BlackPawn
3986                   ? *pieceToSolid(piece)
3987                   : *pieceToOutline(piece),
3988                   dest, wbPieceGC, 0, 0,
3989                   squareSize, squareSize, x, y);
3990         break;
3991     }
3992 }
3993
3994 static void
3995 monoDrawPiece (ChessSquare piece, int square_color, int x, int y, Drawable dest)
3996 {
3997     switch (square_color) {
3998       case 1: /* light */
3999       case 2: /* neutral */
4000       default:
4001         XCopyPlane(xDisplay, (int) piece < (int) BlackPawn
4002                    ? *pieceToOutline(piece)
4003                    : *pieceToSolid(piece),
4004                    dest, bwPieceGC, 0, 0,
4005                    squareSize, squareSize, x, y, 1);
4006         break;
4007       case 0: /* dark */
4008         XCopyPlane(xDisplay, (int) piece < (int) BlackPawn
4009                    ? *pieceToSolid(piece)
4010                    : *pieceToOutline(piece),
4011                    dest, wbPieceGC, 0, 0,
4012                    squareSize, squareSize, x, y, 1);
4013         break;
4014     }
4015 }
4016
4017 static void
4018 colorDrawPiece (ChessSquare piece, int square_color, int x, int y, Drawable dest)
4019 {
4020     if(pieceToSolid(piece) == NULL) return; // [HGM] bitmaps: make it non-fatal if we have no bitmap;
4021     switch (square_color) {
4022       case 1: /* light */
4023         XCopyPlane(xDisplay, *pieceToSolid(piece),
4024                    dest, (int) piece < (int) BlackPawn
4025                    ? wlPieceGC : blPieceGC, 0, 0,
4026                    squareSize, squareSize, x, y, 1);
4027         break;
4028       case 0: /* dark */
4029         XCopyPlane(xDisplay, *pieceToSolid(piece),
4030                    dest, (int) piece < (int) BlackPawn
4031                    ? wdPieceGC : bdPieceGC, 0, 0,
4032                    squareSize, squareSize, x, y, 1);
4033         break;
4034       case 2: /* neutral */
4035       default:
4036         XCopyPlane(xDisplay, *pieceToSolid(piece),
4037                    dest, (int) piece < (int) BlackPawn
4038                    ? wjPieceGC : bjPieceGC, 0, 0,
4039                    squareSize, squareSize, x, y, 1);
4040         break;
4041     }
4042 }
4043
4044 static void
4045 colorDrawPieceImage (ChessSquare piece, int square_color, int x, int y, Drawable dest)
4046 {
4047     int kind, p = piece;
4048
4049     switch (square_color) {
4050       case 1: /* light */
4051       case 2: /* neutral */
4052       default:
4053         if ((int)piece < (int) BlackPawn) {
4054             kind = 0;
4055         } else {
4056             kind = 2;
4057             piece -= BlackPawn;
4058         }
4059         break;
4060       case 0: /* dark */
4061         if ((int)piece < (int) BlackPawn) {
4062             kind = 1;
4063         } else {
4064             kind = 3;
4065             piece -= BlackPawn;
4066         }
4067         break;
4068     }
4069     if(appData.upsideDown && flipView) { kind ^= 2; p += p < BlackPawn ? BlackPawn : -BlackPawn; }// swap white and black pieces
4070     if(useTexture & square_color+1) {
4071         BlankSquare(x, y, square_color, piece, dest, 1); // erase previous contents with background
4072         XSetClipMask(xDisplay, wlPieceGC, xpmMask[p]);
4073         XSetClipOrigin(xDisplay, wlPieceGC, x, y);
4074         XCopyArea(xDisplay, xpmPieceBitmap[kind][piece], dest, wlPieceGC, 0, 0, squareSize, squareSize, x, y);
4075         XSetClipMask(xDisplay, wlPieceGC, None);
4076         XSetClipOrigin(xDisplay, wlPieceGC, 0, 0);
4077     } else
4078     XCopyArea(xDisplay, xpmPieceBitmap[kind][piece],
4079               dest, wlPieceGC, 0, 0,
4080               squareSize, squareSize, x, y);
4081 }
4082
4083 typedef void (*DrawFunc)();
4084
4085 DrawFunc
4086 ChooseDrawFunc ()
4087 {
4088     if (appData.monoMode) {
4089         if (DefaultDepth(xDisplay, xScreen) == 1) {
4090             return monoDrawPiece_1bit;
4091         } else {
4092             return monoDrawPiece;
4093         }
4094     } else {
4095         if (useImages)
4096           return colorDrawPieceImage;
4097         else
4098           return colorDrawPiece;
4099     }
4100 }
4101
4102 /* [HR] determine square color depending on chess variant. */
4103 static int
4104 SquareColor (int row, int column)
4105 {
4106     int square_color;
4107
4108     if (gameInfo.variant == VariantXiangqi) {
4109         if (column >= 3 && column <= 5 && row >= 0 && row <= 2) {
4110             square_color = 1;
4111         } else if (column >= 3 && column <= 5 && row >= 7 && row <= 9) {
4112             square_color = 0;
4113         } else if (row <= 4) {
4114             square_color = 0;
4115         } else {
4116             square_color = 1;
4117         }
4118     } else {
4119         square_color = ((column + row) % 2) == 1;
4120     }
4121
4122     /* [hgm] holdings: next line makes all holdings squares light */
4123     if(column < BOARD_LEFT || column >= BOARD_RGHT) square_color = 1;
4124
4125     return square_color;
4126 }
4127
4128 void
4129 DrawSquare (int row, int column, ChessSquare piece, int do_flash)
4130 {
4131     int square_color, x, y, direction, font_ascent, font_descent;
4132     int i;
4133     char string[2];
4134     XCharStruct overall;
4135     DrawFunc drawfunc;
4136     int flash_delay;
4137
4138     /* Calculate delay in milliseconds (2-delays per complete flash) */
4139     flash_delay = 500 / appData.flashRate;
4140
4141     if (flipView) {
4142         x = lineGap + ((BOARD_WIDTH-1)-column) *
4143           (squareSize + lineGap);
4144         y = lineGap + row * (squareSize + lineGap);
4145     } else {
4146         x = lineGap + column * (squareSize + lineGap);
4147         y = lineGap + ((BOARD_HEIGHT-1)-row) *
4148           (squareSize + lineGap);
4149     }
4150
4151     if(twoBoards && partnerUp) x += hOffset; // [HGM] dual: draw second board
4152
4153     square_color = SquareColor(row, column);
4154
4155     if ( // [HGM] holdings: blank out area between board and holdings
4156                  column == BOARD_LEFT-1 ||  column == BOARD_RGHT
4157               || (column == BOARD_LEFT-2 && row < BOARD_HEIGHT-gameInfo.holdingsSize)
4158                   || (column == BOARD_RGHT+1 && row >= gameInfo.holdingsSize) ) {
4159                         BlankSquare(x, y, 2, EmptySquare, xBoardWindow, 1);
4160
4161                         // [HGM] print piece counts next to holdings
4162                         string[1] = NULLCHAR;
4163                         if (column == (flipView ? BOARD_LEFT-1 : BOARD_RGHT) && piece > 1 ) {
4164                             string[0] = '0' + piece;
4165                             XTextExtents(countFontStruct, string, 1, &direction,
4166                                  &font_ascent, &font_descent, &overall);
4167                             if (appData.monoMode) {
4168                                 XDrawImageString(xDisplay, xBoardWindow, countGC,
4169                                                  x + squareSize - overall.width - 2,
4170                                                  y + font_ascent + 1, string, 1);
4171                             } else {
4172                                 XDrawString(xDisplay, xBoardWindow, countGC,
4173                                             x + squareSize - overall.width - 2,
4174                                             y + font_ascent + 1, string, 1);
4175                             }
4176                         }
4177                         if (column == (flipView ? BOARD_RGHT : BOARD_LEFT-1) && piece > 1) {
4178                             string[0] = '0' + piece;
4179                             XTextExtents(countFontStruct, string, 1, &direction,
4180                                          &font_ascent, &font_descent, &overall);
4181                             if (appData.monoMode) {
4182                                 XDrawImageString(xDisplay, xBoardWindow, countGC,
4183                                                  x + 2, y + font_ascent + 1, string, 1);
4184                             } else {
4185                                 XDrawString(xDisplay, xBoardWindow, countGC,
4186                                             x + 2, y + font_ascent + 1, string, 1);
4187                             }
4188                         }
4189     } else {
4190             if (piece == EmptySquare || appData.blindfold) {
4191                         BlankSquare(x, y, square_color, piece, xBoardWindow, 1);
4192             } else {
4193                         drawfunc = ChooseDrawFunc();
4194
4195                         if (do_flash && appData.flashCount > 0) {
4196                             for (i=0; i<appData.flashCount; ++i) {
4197                                         drawfunc(piece, square_color, x, y, xBoardWindow);
4198                                         XSync(xDisplay, False);
4199                                         do_flash_delay(flash_delay);
4200
4201                                         BlankSquare(x, y, square_color, piece, xBoardWindow, 1);
4202                                         XSync(xDisplay, False);
4203                                         do_flash_delay(flash_delay);
4204                             }
4205                         }
4206                         drawfunc(piece, square_color, x, y, xBoardWindow);
4207         }
4208         }
4209
4210     string[1] = NULLCHAR;
4211     if (appData.showCoords && row == (flipView ? BOARD_HEIGHT-1 : 0)
4212                 && column >= BOARD_LEFT && column < BOARD_RGHT) {
4213         string[0] = 'a' + column - BOARD_LEFT;
4214         XTextExtents(coordFontStruct, string, 1, &direction,
4215                      &font_ascent, &font_descent, &overall);
4216         if (appData.monoMode) {
4217             XDrawImageString(xDisplay, xBoardWindow, coordGC,
4218                              x + squareSize - overall.width - 2,
4219                              y + squareSize - font_descent - 1, string, 1);
4220         } else {
4221             XDrawString(xDisplay, xBoardWindow, coordGC,
4222                         x + squareSize - overall.width - 2,
4223                         y + squareSize - font_descent - 1, string, 1);
4224         }
4225     }
4226     if (appData.showCoords && column == (flipView ? BOARD_RGHT-1 : BOARD_LEFT)) {
4227         string[0] = ONE + row;
4228         XTextExtents(coordFontStruct, string, 1, &direction,
4229                      &font_ascent, &font_descent, &overall);
4230         if (appData.monoMode) {
4231             XDrawImageString(xDisplay, xBoardWindow, coordGC,
4232                              x + 2, y + font_ascent + 1, string, 1);
4233         } else {
4234             XDrawString(xDisplay, xBoardWindow, coordGC,
4235                         x + 2, y + font_ascent + 1, string, 1);
4236         }
4237     }
4238     if(!partnerUp && marker[row][column]) {
4239         if(appData.monoMode) {
4240             XFillArc(xDisplay, xBoardWindow, marker[row][column] == 2 ? darkSquareGC : lightSquareGC,
4241                     x + squareSize/4, y+squareSize/4, squareSize/2, squareSize/2, 0, 64*360);
4242             XDrawArc(xDisplay, xBoardWindow, marker[row][column] == 2 ? lightSquareGC : darkSquareGC,
4243                     x + squareSize/4, y+squareSize/4, squareSize/2, squareSize/2, 0, 64*360);
4244         } else
4245         XFillArc(xDisplay, xBoardWindow, marker[row][column] == 2 ? prelineGC : highlineGC,
4246                 x + squareSize/4, y+squareSize/4, squareSize/2, squareSize/2, 0, 64*360);
4247     }
4248 }
4249
4250 double
4251 Fraction (int x, int start, int stop)
4252 {
4253    double f = ((double) x - start)/(stop - start);
4254    if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
4255    return f;
4256 }
4257
4258 static WindowPlacement wpNew;
4259
4260 void
4261 CoDrag (Widget sh, WindowPlacement *wp)
4262 {
4263     Arg args[16];
4264     int j=0, touch=0, fudge = 2;
4265     GetActualPlacement(sh, wp);
4266     if(abs(wpMain.x + wpMain.width + 2*frameX - wp->x)         < fudge) touch = 1; else // right touch
4267     if(abs(wp->x + wp->width + 2*frameX - wpMain.x)            < fudge) touch = 2; else // left touch
4268     if(abs(wpMain.y + wpMain.height + frameX + frameY - wp->y) < fudge) touch = 3; else // bottom touch
4269     if(abs(wp->y + wp->height + frameX + frameY - wpMain.y)    < fudge) touch = 4;      // top touch
4270     if(!touch ) return; // only windows that touch co-move
4271     if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
4272         int heightInc = wpNew.height - wpMain.height;
4273         double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
4274         double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
4275         wp->y += fracTop * heightInc;
4276         heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
4277         if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
4278     } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
4279         int widthInc = wpNew.width - wpMain.width;
4280         double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
4281         double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
4282         wp->y += fracLeft * widthInc;
4283         widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
4284         if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
4285     }
4286     wp->x += wpNew.x - wpMain.x;
4287     wp->y += wpNew.y - wpMain.y;
4288     if(touch == 1) wp->x += wpNew.width - wpMain.width; else
4289     if(touch == 3) wp->y += wpNew.height - wpMain.height;
4290     XtSetArg(args[j], XtNx, wp->x); j++;
4291     XtSetArg(args[j], XtNy, wp->y); j++;
4292     XtSetValues(sh, args, j);
4293 }
4294
4295 static XtIntervalId delayedDragID = 0;
4296
4297 void
4298 DragProc ()
4299 {
4300         GetActualPlacement(shellWidget, &wpNew);
4301         if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
4302            wpNew.width == wpMain.width && wpNew.height == wpMain.height) // not sized
4303             return; // false alarm
4304         if(EngineOutputIsUp()) CoDrag(engineOutputShell, &wpEngineOutput);
4305         if(MoveHistoryIsUp()) CoDrag(shells[7], &wpMoveHistory);
4306         if(EvalGraphIsUp()) CoDrag(evalGraphShell, &wpEvalGraph);
4307         if(GameListIsUp()) CoDrag(gameListShell, &wpGameList);
4308         wpMain = wpNew;
4309         XDrawPosition(boardWidget, True, NULL);
4310         delayedDragID = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
4311 }
4312
4313
4314 void
4315 DelayedDrag ()
4316 {
4317     if(delayedDragID) XtRemoveTimeOut(delayedDragID); // cancel pending
4318     delayedDragID =
4319       XtAppAddTimeOut(appContext, 50, (XtTimerCallbackProc) DragProc, (XtPointer) 0); // and schedule new one 50 msec later
4320 }
4321
4322 /* Why is this needed on some versions of X? */
4323 void
4324 EventProc (Widget widget, caddr_t unused, XEvent *event)
4325 {
4326     if (!XtIsRealized(widget))
4327       return;
4328     switch (event->type) {
4329       case ConfigureNotify: // main window is being dragged: drag attached windows with it
4330         if(appData.useStickyWindows)
4331             DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
4332         break;
4333       case Expose:
4334         if (event->xexpose.count > 0) return;  /* no clipping is done */
4335         XDrawPosition(widget, True, NULL);
4336         if(twoBoards) { // [HGM] dual: draw other board in other orientation
4337             flipView = !flipView; partnerUp = !partnerUp;
4338             XDrawPosition(widget, True, NULL);
4339             flipView = !flipView; partnerUp = !partnerUp;
4340         }
4341         break;
4342       case MotionNotify:
4343         if(SeekGraphClick(Press, event->xbutton.x, event->xbutton.y, 1)) break;
4344       default:
4345         return;
4346     }
4347 }
4348 /* end why */
4349
4350 void
4351 DrawPosition (int fullRedraw, Board board)
4352 {
4353     XDrawPosition(boardWidget, fullRedraw, board);
4354 }
4355
4356 /* Returns 1 if there are "too many" differences between b1 and b2
4357    (i.e. more than 1 move was made) */
4358 static int
4359 too_many_diffs (Board b1, Board b2)
4360 {
4361     int i, j;
4362     int c = 0;
4363
4364     for (i=0; i<BOARD_HEIGHT; ++i) {
4365         for (j=0; j<BOARD_WIDTH; ++j) {
4366             if (b1[i][j] != b2[i][j]) {
4367                 if (++c > 4)    /* Castling causes 4 diffs */
4368                   return 1;
4369             }
4370         }
4371     }
4372     return 0;
4373 }
4374
4375 /* Matrix describing castling maneuvers */
4376 /* Row, ColRookFrom, ColKingFrom, ColRookTo, ColKingTo */
4377 static int castling_matrix[4][5] = {
4378     { 0, 0, 4, 3, 2 },          /* 0-0-0, white */
4379     { 0, 7, 4, 5, 6 },          /* 0-0,   white */
4380     { 7, 0, 4, 3, 2 },          /* 0-0-0, black */
4381     { 7, 7, 4, 5, 6 }           /* 0-0,   black */
4382 };
4383
4384 /* Checks whether castling occurred. If it did, *rrow and *rcol
4385    are set to the destination (row,col) of the rook that moved.
4386
4387    Returns 1 if castling occurred, 0 if not.
4388
4389    Note: Only handles a max of 1 castling move, so be sure
4390    to call too_many_diffs() first.
4391    */
4392 static int
4393 check_castle_draw (Board newb, Board oldb, int *rrow, int *rcol)
4394 {
4395     int i, *r, j;
4396     int match;
4397
4398     /* For each type of castling... */
4399     for (i=0; i<4; ++i) {
4400         r = castling_matrix[i];
4401
4402         /* Check the 4 squares involved in the castling move */
4403         match = 0;
4404         for (j=1; j<=4; ++j) {
4405             if (newb[r[0]][r[j]] == oldb[r[0]][r[j]]) {
4406                 match = 1;
4407                 break;
4408             }
4409         }
4410
4411         if (!match) {
4412             /* All 4 changed, so it must be a castling move */
4413             *rrow = r[0];
4414             *rcol = r[3];
4415             return 1;
4416         }
4417     }
4418     return 0;
4419 }
4420
4421 // [HGM] seekgraph: some low-level drawing routines cloned from xevalgraph
4422 void
4423 DrawSeekAxis (int x, int y, int xTo, int yTo)
4424 {
4425       XDrawLine(xDisplay, xBoardWindow, lineGC, x, y, xTo, yTo);
4426 }
4427
4428 void
4429 DrawSeekBackground (int left, int top, int right, int bottom)
4430 {
4431     XFillRectangle(xDisplay, xBoardWindow, lightSquareGC, left, top, right-left, bottom-top);
4432 }
4433
4434 void
4435 DrawSeekText (char *buf, int x, int y)
4436 {
4437     XDrawString(xDisplay, xBoardWindow, coordGC, x, y+4, buf, strlen(buf));
4438 }
4439
4440 void
4441 DrawSeekDot (int x, int y, int colorNr)
4442 {
4443     int square = colorNr & 0x80;
4444     GC color;
4445     colorNr &= 0x7F;
4446     color = colorNr == 0 ? prelineGC : colorNr == 1 ? darkSquareGC : highlineGC;
4447     if(square)
4448         XFillRectangle(xDisplay, xBoardWindow, color,
4449                 x-squareSize/9, y-squareSize/9, 2*squareSize/9, 2*squareSize/9);
4450     else
4451         XFillArc(xDisplay, xBoardWindow, color,
4452                 x-squareSize/8, y-squareSize/8, squareSize/4, squareSize/4, 0, 64*360);
4453 }
4454
4455 static int damage[2][BOARD_RANKS][BOARD_FILES];
4456
4457 /*
4458  * event handler for redrawing the board
4459  */
4460 void
4461 XDrawPosition (Widget w, int repaint, Board board)
4462 {
4463     int i, j, do_flash;
4464     static int lastFlipView = 0;
4465     static int lastBoardValid[2] = {0, 0};
4466     static Board lastBoard[2];
4467     Arg args[16];
4468     int rrow, rcol;
4469     int nr = twoBoards*partnerUp;
4470
4471     if(DrawSeekGraph()) return; // [HGM] seekgraph: suppress any drawing if seek graph up
4472
4473     if (board == NULL) {
4474         if (!lastBoardValid[nr]) return;
4475         board = lastBoard[nr];
4476     }
4477     if (!lastBoardValid[nr] || (nr == 0 && lastFlipView != flipView)) {
4478         MarkMenuItem("Flip View", flipView);
4479     }
4480
4481     /*
4482      * It would be simpler to clear the window with XClearWindow()
4483      * but this causes a very distracting flicker.
4484      */
4485
4486     if (!repaint && lastBoardValid[nr] && (nr == 1 || lastFlipView == flipView)) {
4487
4488         if ( lineGap && IsDrawArrowEnabled())
4489             XDrawSegments(xDisplay, xBoardWindow, lineGC,
4490                         gridSegments, BOARD_HEIGHT + BOARD_WIDTH + 2);
4491
4492         /* If too much changes (begin observing new game, etc.), don't
4493            do flashing */
4494         do_flash = too_many_diffs(board, lastBoard[nr]) ? 0 : 1;
4495
4496         /* Special check for castling so we don't flash both the king
4497            and the rook (just flash the king). */
4498         if (do_flash) {
4499             if (check_castle_draw(board, lastBoard[nr], &rrow, &rcol)) {
4500                 /* Draw rook with NO flashing. King will be drawn flashing later */
4501                 DrawSquare(rrow, rcol, board[rrow][rcol], 0);
4502                 lastBoard[nr][rrow][rcol] = board[rrow][rcol];
4503             }
4504         }
4505
4506         /* First pass -- Draw (newly) empty squares and repair damage.
4507            This prevents you from having a piece show up twice while it
4508            is flashing on its new square */
4509         for (i = 0; i < BOARD_HEIGHT; i++)
4510           for (j = 0; j < BOARD_WIDTH; j++)
4511             if ((board[i][j] != lastBoard[nr][i][j] && board[i][j] == EmptySquare)
4512                 || damage[nr][i][j]) {
4513                 DrawSquare(i, j, board[i][j], 0);
4514                 damage[nr][i][j] = False;
4515             }
4516
4517         /* Second pass -- Draw piece(s) in new position and flash them */
4518         for (i = 0; i < BOARD_HEIGHT; i++)
4519           for (j = 0; j < BOARD_WIDTH; j++)
4520             if (board[i][j] != lastBoard[nr][i][j]) {
4521                 DrawSquare(i, j, board[i][j], do_flash);
4522             }
4523     } else {
4524         if (lineGap > 0)
4525           XDrawSegments(xDisplay, xBoardWindow, lineGC,
4526                         twoBoards & partnerUp ? secondSegments : // [HGM] dual
4527                         gridSegments, BOARD_HEIGHT + BOARD_WIDTH + 2);
4528
4529         for (i = 0; i < BOARD_HEIGHT; i++)
4530           for (j = 0; j < BOARD_WIDTH; j++) {
4531               DrawSquare(i, j, board[i][j], 0);
4532               damage[nr][i][j] = False;
4533           }
4534     }
4535
4536     CopyBoard(lastBoard[nr], board);
4537     lastBoardValid[nr] = 1;
4538   if(nr == 0) { // [HGM] dual: no highlights on second board yet
4539     lastFlipView = flipView;
4540
4541     /* Draw highlights */
4542     if (pm1X >= 0 && pm1Y >= 0) {
4543       drawHighlight(pm1X, pm1Y, prelineGC);
4544     }
4545     if (pm2X >= 0 && pm2Y >= 0) {
4546       drawHighlight(pm2X, pm2Y, prelineGC);
4547     }
4548     if (hi1X >= 0 && hi1Y >= 0) {
4549       drawHighlight(hi1X, hi1Y, highlineGC);
4550     }
4551     if (hi2X >= 0 && hi2Y >= 0) {
4552       drawHighlight(hi2X, hi2Y, highlineGC);
4553     }
4554     DrawArrowHighlight(hi1X, hi1Y, hi2X, hi2Y);
4555   }
4556     /* If piece being dragged around board, must redraw that too */
4557     DrawDragPiece();
4558
4559     XSync(xDisplay, False);
4560 }
4561
4562
4563 /*
4564  * event handler for redrawing the board
4565  */
4566 void
4567 DrawPositionProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
4568 {
4569     XDrawPosition(w, True, NULL);
4570 }
4571
4572
4573 /*
4574  * event handler for parsing user moves
4575  */
4576 // [HGM] This routine will need quite some reworking. Although the backend still supports the old
4577 //       way of doing things, by calling UserMoveEvent() to test the legality of the move and then perform
4578 //       it at the end, and doing all kind of preliminary tests here (e.g. to weed out self-captures), it
4579 //       should be made to use the new way, of calling UserMoveTest early  to determine the legality of the
4580 //       move, (which will weed out the illegal selfcaptures and moves into the holdings, and flag promotions),
4581 //       and at the end FinishMove() to perform the move after optional promotion popups.
4582 //       For now I patched it to allow self-capture with King, and suppress clicks between board and holdings.
4583 void
4584 HandleUserMove (Widget w, XEvent *event, String *prms, Cardinal *nprms)
4585 {
4586     if (w != boardWidget || errorExitStatus != -1) return;
4587     if(nprms) shiftKey = !strcmp(prms[0], "1");
4588
4589     if (promotionUp) {
4590         if (event->type == ButtonPress) {
4591             XtPopdown(promotionShell);
4592             XtDestroyWidget(promotionShell);
4593             promotionUp = False;
4594             ClearHighlights();
4595             fromX = fromY = -1;
4596         } else {
4597             return;
4598         }
4599     }
4600
4601     // [HGM] mouse: the rest of the mouse handler is moved to the backend, and called here
4602     if(event->type == ButtonPress)   LeftClick(Press,   event->xbutton.x, event->xbutton.y);
4603     if(event->type == ButtonRelease) LeftClick(Release, event->xbutton.x, event->xbutton.y);
4604 }
4605
4606 void
4607 AnimateUserMove (Widget w, XEvent *event, String *params, Cardinal *nParams)
4608 {
4609     if(!PromoScroll(event->xmotion.x, event->xmotion.y))
4610     DragPieceMove(event->xmotion.x, event->xmotion.y);
4611 }
4612
4613 void
4614 HandlePV (Widget w, XEvent * event, String * params, Cardinal * nParams)
4615 {   // [HGM] pv: walk PV
4616     MovePV(event->xmotion.x, event->xmotion.y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
4617 }
4618
4619 static int savedIndex;  /* gross that this is global */
4620
4621 void
4622 CommentClick (Widget w, XEvent * event, String * params, Cardinal * nParams)
4623 {
4624         String val;
4625         XawTextPosition index, dummy;
4626         Arg arg;
4627
4628         XawTextGetSelectionPos(w, &index, &dummy);
4629         XtSetArg(arg, XtNstring, &val);
4630         XtGetValues(w, &arg, 1);
4631         ReplaceComment(savedIndex, val);
4632         if(savedIndex != currentMove) ToNrEvent(savedIndex);
4633         LoadVariation( index, val ); // [HGM] also does the actual moving to it, now
4634 }
4635
4636 void
4637 EditCommentPopUp (int index, char *title, char *text)
4638 {
4639     savedIndex = index;
4640     if (text == NULL) text = "";
4641     NewCommentPopup(title, text, index);
4642 }
4643
4644 void
4645 ICSInputBoxPopUp ()
4646 {
4647     InputBoxPopup();
4648 }
4649
4650 extern Option boxOptions[];
4651
4652 void
4653 ICSInputSendText ()
4654 {
4655     Widget edit;
4656     int j;
4657     Arg args[16];
4658     String val;
4659
4660     edit = boxOptions[0].handle;
4661     j = 0;
4662     XtSetArg(args[j], XtNstring, &val); j++;
4663     XtGetValues(edit, args, j);
4664     SaveInHistory(val);
4665     SendMultiLineToICS(val);
4666     XtCallActionProc(edit, "select-all", NULL, NULL, 0);
4667     XtCallActionProc(edit, "kill-selection", NULL, NULL, 0);
4668 }
4669
4670 void
4671 ICSInputBoxPopDown ()
4672 {
4673     PopDown(4);
4674 }
4675
4676 void
4677 CommentPopUp (char *title, char *text)
4678 {
4679     savedIndex = currentMove; // [HGM] vari
4680     NewCommentPopup(title, text, currentMove);
4681 }
4682
4683 void
4684 CommentPopDown ()
4685 {
4686     PopDown(1);
4687 }
4688
4689 static char *openName;
4690 FILE *openFP;
4691
4692 void
4693 DelayedLoad ()
4694 {
4695   (void) (*fileProc)(openFP, 0, openName);
4696 }
4697
4698 void
4699 FileNamePopUp (char *label, char *def, char *filter, FileProc proc, char *openMode)
4700 {
4701     fileProc = proc;            /* I can't see a way not */
4702     fileOpenMode = openMode;    /*   to use globals here */
4703     {   // [HGM] use file-selector dialog stolen from Ghostview
4704         int index; // this is not supported yet
4705         if(openFP = XsraSelFile(shellWidget, label, NULL, NULL, _("could not open: "),
4706                            (def[0] ? def : NULL), filter, openMode, NULL, &openName))
4707           // [HGM] delay to give expose event opportunity to redraw board after browser-dialog popdown before lengthy load starts
4708           ScheduleDelayedEvent(&DelayedLoad, 50);
4709     }
4710 }
4711
4712 void
4713 FileNamePopDown ()
4714 {
4715     if (!filenameUp) return;
4716     XtPopdown(fileNameShell);
4717     XtDestroyWidget(fileNameShell);
4718     filenameUp = False;
4719     ModeHighlight();
4720 }
4721
4722 void
4723 FileNameCallback (Widget w, XtPointer client_data, XtPointer call_data)
4724 {
4725     String name;
4726     Arg args[16];
4727
4728     XtSetArg(args[0], XtNlabel, &name);
4729     XtGetValues(w, args, 1);
4730
4731     if (strcmp(name, _("cancel")) == 0) {
4732         FileNamePopDown();
4733         return;
4734     }
4735
4736     FileNameAction(w, NULL, NULL, NULL);
4737 }
4738
4739 void
4740 FileNameAction (Widget w, XEvent *event, String *prms, Cardinal *nprms)
4741 {
4742     char buf[MSG_SIZ];
4743     String name;
4744     FILE *f;
4745     char *p, *fullname;
4746     int index;
4747
4748     name = XawDialogGetValueString(w = XtParent(w));
4749
4750     if ((name != NULL) && (*name != NULLCHAR)) {
4751         safeStrCpy(buf, name, sizeof(buf)/sizeof(buf[0]) );
4752         XtPopdown(w = XtParent(XtParent(w)));
4753         XtDestroyWidget(w);
4754         filenameUp = False;
4755
4756         p = strrchr(buf, ' ');
4757         if (p == NULL) {
4758             index = 0;
4759         } else {
4760             *p++ = NULLCHAR;
4761             index = atoi(p);
4762         }
4763         fullname = ExpandPathName(buf);
4764         if (!fullname) {
4765             ErrorPopUp(_("Error"), _("Can't open file"), FALSE);
4766         }
4767         else {
4768             f = fopen(fullname, fileOpenMode);
4769             if (f == NULL) {
4770                 DisplayError(_("Failed to open file"), errno);
4771             } else {
4772                 (void) (*fileProc)(f, index, buf);
4773             }
4774         }
4775         ModeHighlight();
4776         return;
4777     }
4778
4779     XtPopdown(w = XtParent(XtParent(w)));
4780     XtDestroyWidget(w);
4781     filenameUp = False;
4782     ModeHighlight();
4783 }
4784
4785 void
4786 PromotionPopUp ()
4787 {
4788     Arg args[16];
4789     Widget dialog, layout;
4790     Position x, y;
4791     Dimension bw_width, pw_width;
4792     int j;
4793     char *PromoChars = "wglcqrbnkac+=\0";
4794
4795     j = 0;
4796     XtSetArg(args[j], XtNwidth, &bw_width); j++;
4797     XtGetValues(boardWidget, args, j);
4798
4799     j = 0;
4800     XtSetArg(args[j], XtNresizable, True); j++;
4801     XtSetArg(args[j], XtNtitle, XtNewString(_("Promotion"))); j++;
4802     promotionShell =
4803       XtCreatePopupShell("Promotion", transientShellWidgetClass,
4804                          shellWidget, args, j);
4805     layout =
4806       XtCreateManagedWidget(layoutName, formWidgetClass, promotionShell,
4807                             layoutArgs, XtNumber(layoutArgs));
4808
4809     j = 0;
4810     XtSetArg(args[j], XtNlabel, _("Promote to what?")); j++;
4811     XtSetArg(args[j], XtNborderWidth, 0); j++;
4812     dialog = XtCreateManagedWidget("promotion", dialogWidgetClass,
4813                                    layout, args, j);
4814
4815   if(gameInfo.variant != VariantShogi) {
4816    if(gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove)) {
4817       XawDialogAddButton(dialog, _("Warlord"), PromotionCallback, PromoChars + 0);
4818       XawDialogAddButton(dialog, _("General"), PromotionCallback, PromoChars + 1);
4819       XawDialogAddButton(dialog, _("Lieutenant"), PromotionCallback, PromoChars + 2);
4820       XawDialogAddButton(dialog, _("Captain"), PromotionCallback, PromoChars + 3);
4821     } else {
4822     XawDialogAddButton(dialog, _("Queen"), PromotionCallback, PromoChars + 4);
4823     XawDialogAddButton(dialog, _("Rook"), PromotionCallback, PromoChars + 5);
4824     XawDialogAddButton(dialog, _("Bishop"), PromotionCallback, PromoChars + 6);
4825     XawDialogAddButton(dialog, _("Knight"), PromotionCallback, PromoChars + 7);
4826     }
4827     if (!appData.testLegality || gameInfo.variant == VariantSuicide ||
4828         gameInfo.variant == VariantSpartan && !WhiteOnMove(currentMove) ||
4829         gameInfo.variant == VariantGiveaway) {
4830       XawDialogAddButton(dialog, _("King"), PromotionCallback, PromoChars + 8);
4831     }
4832     if(gameInfo.variant == VariantCapablanca ||
4833        gameInfo.variant == VariantGothic ||
4834        gameInfo.variant == VariantCapaRandom) {
4835       XawDialogAddButton(dialog, _("Archbishop"), PromotionCallback, PromoChars + 9);
4836       XawDialogAddButton(dialog, _("Chancellor"), PromotionCallback, PromoChars + 10);
4837     }
4838   } else // [HGM] shogi
4839   {
4840       XawDialogAddButton(dialog, _("Promote"), PromotionCallback, PromoChars + 11);
4841       XawDialogAddButton(dialog, _("Defer"), PromotionCallback, PromoChars + 12);
4842   }
4843     XawDialogAddButton(dialog, _("cancel"), PromotionCallback, PromoChars + 13);
4844
4845     XtRealizeWidget(promotionShell);
4846     CatchDeleteWindow(promotionShell, "PromotionPopDown");
4847
4848     j = 0;
4849     XtSetArg(args[j], XtNwidth, &pw_width); j++;
4850     XtGetValues(promotionShell, args, j);
4851
4852     XtTranslateCoords(boardWidget, (bw_width - pw_width) / 2,
4853                       lineGap + squareSize/3 +
4854                       ((toY == BOARD_HEIGHT-1) ^ (flipView) ?
4855                        0 : 6*(squareSize + lineGap)), &x, &y);
4856
4857     j = 0;
4858     XtSetArg(args[j], XtNx, x); j++;
4859     XtSetArg(args[j], XtNy, y); j++;
4860     XtSetValues(promotionShell, args, j);
4861
4862     XtPopup(promotionShell, XtGrabNone);
4863
4864     promotionUp = True;
4865 }
4866
4867 void
4868 PromotionPopDown ()
4869 {
4870     if (!promotionUp) return;
4871     XtPopdown(promotionShell);
4872     XtDestroyWidget(promotionShell);
4873     promotionUp = False;
4874 }
4875
4876 void
4877 PromotionCallback (Widget w, XtPointer client_data, XtPointer call_data)
4878 {
4879     int promoChar = * (const char *) client_data;
4880
4881     PromotionPopDown();
4882
4883     if (fromX == -1) return;
4884
4885     if (! promoChar) {
4886         fromX = fromY = -1;
4887         ClearHighlights();
4888         return;
4889     }
4890     UserMoveEvent(fromX, fromY, toX, toY, promoChar);
4891
4892     if (!appData.highlightLastMove || gotPremove) ClearHighlights();
4893     if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
4894     fromX = fromY = -1;
4895 }
4896
4897
4898 void
4899 ErrorCallback (Widget w, XtPointer client_data, XtPointer call_data)
4900 {
4901     dialogError = errorUp = False;
4902     XtPopdown(w = XtParent(XtParent(XtParent(w))));
4903     XtDestroyWidget(w);
4904     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
4905 }
4906
4907
4908 void
4909 ErrorPopDown ()
4910 {
4911     if (!errorUp) return;
4912     dialogError = errorUp = False;
4913     XtPopdown(errorShell);
4914     XtDestroyWidget(errorShell);
4915     if (errorExitStatus != -1) ExitEvent(errorExitStatus);
4916 }
4917
4918 void
4919 ErrorPopUp (char *title, char *label, int modal)
4920 {
4921     Arg args[16];
4922     Widget dialog, layout;
4923     Position x, y;
4924     int xx, yy;
4925     Window junk;
4926     Dimension bw_width, pw_width;
4927     Dimension pw_height;
4928     int i;
4929
4930     i = 0;
4931     XtSetArg(args[i], XtNresizable, True);  i++;
4932     XtSetArg(args[i], XtNtitle, title); i++;
4933     errorShell =
4934       XtCreatePopupShell("errorpopup", transientShellWidgetClass,
4935                          shellUp[0] ? (dialogError = modal = TRUE, shells[0]) : shellWidget, args, i);
4936     layout =
4937       XtCreateManagedWidget(layoutName, formWidgetClass, errorShell,
4938                             layoutArgs, XtNumber(layoutArgs));
4939
4940     i = 0;
4941     XtSetArg(args[i], XtNlabel, label); i++;
4942     XtSetArg(args[i], XtNborderWidth, 0); i++;
4943     dialog = XtCreateManagedWidget("dialog", dialogWidgetClass,
4944                                    layout, args, i);
4945
4946     XawDialogAddButton(dialog, _("ok"), ErrorCallback, (XtPointer) dialog);
4947
4948     XtRealizeWidget(errorShell);
4949     CatchDeleteWindow(errorShell, "ErrorPopDown");
4950
4951     i = 0;
4952     XtSetArg(args[i], XtNwidth, &bw_width);  i++;
4953     XtGetValues(boardWidget, args, i);
4954     i = 0;
4955     XtSetArg(args[i], XtNwidth, &pw_width);  i++;
4956     XtSetArg(args[i], XtNheight, &pw_height);  i++;
4957     XtGetValues(errorShell, args, i);
4958
4959 #ifdef NOTDEF
4960     /* This code seems to tickle an X bug if it is executed too soon
4961        after xboard starts up.  The coordinates get transformed as if
4962        the main window was positioned at (0, 0).
4963        */
4964     XtTranslateCoords(boardWidget, (bw_width - pw_width) / 2,
4965                       0 - pw_height + squareSize / 3, &x, &y);
4966 #else
4967     XTranslateCoordinates(xDisplay, XtWindow(boardWidget),
4968                           RootWindowOfScreen(XtScreen(boardWidget)),
4969                           (bw_width - pw_width) / 2,
4970                           0 - pw_height + squareSize / 3, &xx, &yy, &junk);
4971     x = xx;
4972     y = yy;
4973 #endif
4974     if (y < 0) y = 0; /*avoid positioning top offscreen*/
4975
4976     i = 0;
4977     XtSetArg(args[i], XtNx, x);  i++;
4978     XtSetArg(args[i], XtNy, y);  i++;
4979     XtSetValues(errorShell, args, i);
4980
4981     errorUp = True;
4982     XtPopup(errorShell, modal ? XtGrabExclusive : XtGrabNone);
4983 }
4984
4985 /* Disable all user input other than deleting the window */
4986 static int frozen = 0;
4987
4988 void
4989 FreezeUI ()
4990 {
4991   if (frozen) return;
4992   /* Grab by a widget that doesn't accept input */
4993   XtAddGrab(messageWidget, TRUE, FALSE);
4994   frozen = 1;
4995 }
4996
4997 /* Undo a FreezeUI */
4998 void
4999 ThawUI ()
5000 {
5001   if (!frozen) return;
5002   XtRemoveGrab(messageWidget);
5003   frozen = 0;
5004 }
5005
5006 char *
5007 ModeToWidgetName (GameMode mode)
5008 {
5009     switch (mode) {
5010       case BeginningOfGame:
5011         if (appData.icsActive)
5012           return "ICS Client";
5013         else if (appData.noChessProgram ||
5014                  *appData.cmailGameName != NULLCHAR)
5015           return "Edit Game";
5016         else
5017           return "Machine Black";
5018       case MachinePlaysBlack:
5019         return "Machine Black";
5020       case MachinePlaysWhite:
5021         return "Machine White";
5022       case AnalyzeMode:
5023         return "Analysis Mode";
5024       case AnalyzeFile:
5025         return "Analyze File";
5026       case TwoMachinesPlay:
5027         return "Two Machines";
5028       case EditGame:
5029         return "Edit Game";
5030       case PlayFromGameFile:
5031         return "Load Game";
5032       case EditPosition:
5033         return "Edit Position";
5034       case Training:
5035         return "Training";
5036       case IcsPlayingWhite:
5037       case IcsPlayingBlack:
5038       case IcsObserving:
5039       case IcsIdle:
5040       case IcsExamining:
5041         return "ICS Client";
5042       default:
5043       case EndOfGame:
5044         return NULL;
5045     }
5046 }
5047
5048 void
5049 ModeHighlight ()
5050 {
5051     Arg args[16];
5052     static int oldPausing = FALSE;
5053     static GameMode oldmode = (GameMode) -1;
5054     char *wname;
5055
5056     if (!boardWidget || !XtIsRealized(boardWidget)) return;
5057
5058     if (pausing != oldPausing) {
5059         oldPausing = pausing;
5060         MarkMenuItem("Pause", pausing);
5061
5062         if (appData.showButtonBar) {
5063           /* Always toggle, don't set.  Previous code messes up when
5064              invoked while the button is pressed, as releasing it
5065              toggles the state again. */
5066           {
5067             Pixel oldbg, oldfg;
5068             XtSetArg(args[0], XtNbackground, &oldbg);
5069             XtSetArg(args[1], XtNforeground, &oldfg);
5070             XtGetValues(XtNameToWidget(buttonBarWidget, PAUSE_BUTTON),
5071                         args, 2);
5072             XtSetArg(args[0], XtNbackground, oldfg);
5073             XtSetArg(args[1], XtNforeground, oldbg);
5074           }
5075           XtSetValues(XtNameToWidget(buttonBarWidget, PAUSE_BUTTON), args, 2);
5076         }
5077     }
5078
5079     wname = ModeToWidgetName(oldmode);
5080     if (wname != NULL) {
5081         MarkMenuItem(wname, False);
5082     }
5083     wname = ModeToWidgetName(gameMode);
5084     if (wname != NULL) {
5085         MarkMenuItem(wname, True);
5086     }
5087     oldmode = gameMode;
5088     MarkMenuItem("Machine Match", matchMode && matchGame < appData.matchGames);
5089
5090     /* Maybe all the enables should be handled here, not just this one */
5091     EnableMenuItem("Training", gameMode == Training || gameMode == PlayFromGameFile);
5092 }
5093
5094
5095 /*
5096  * Button/menu procedures
5097  */
5098 int
5099 LoadGamePopUp (FILE *f, int gameNumber, char *title)
5100 {
5101     cmailMsgLoaded = FALSE;
5102     if (gameNumber == 0) {
5103         int error = GameListBuild(f);
5104         if (error) {
5105             DisplayError(_("Cannot build game list"), error);
5106         } else if (!ListEmpty(&gameList) &&
5107                    ((ListGame *) gameList.tailPred)->number > 1) {
5108             GameListPopUp(f, title);
5109             return TRUE;
5110         }
5111         GameListDestroy();
5112         gameNumber = 1;
5113     }
5114     return LoadGame(f, gameNumber, title, FALSE);
5115 }
5116
5117 void
5118 LoadGameProc ()
5119 {
5120     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
5121         Reset(FALSE, TRUE);
5122     }
5123     FileNamePopUp(_("Load game file name?"), "", ".pgn .game", LoadGamePopUp, "rb");
5124 }
5125
5126 void
5127 LoadNextGameProc ()
5128 {
5129     ReloadGame(1);
5130 }
5131
5132 void
5133 LoadPrevGameProc ()
5134 {
5135     ReloadGame(-1);
5136 }
5137
5138 void
5139 ReloadGameProc ()
5140 {
5141     ReloadGame(0);
5142 }
5143
5144 void
5145 LoadNextPositionProc ()
5146 {
5147     ReloadPosition(1);
5148 }
5149
5150 void
5151 LoadPrevPositionProc ()
5152 {
5153     ReloadPosition(-1);
5154 }
5155
5156 void
5157 ReloadPositionProc ()
5158 {
5159     ReloadPosition(0);
5160 }
5161
5162 void
5163 LoadPositionProc() 
5164 {
5165     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
5166         Reset(FALSE, TRUE);
5167     }
5168     FileNamePopUp(_("Load position file name?"), "", ".fen .epd .pos", LoadPosition, "rb");
5169 }
5170
5171 void
5172 SaveGameProc ()
5173 {
5174     FileNamePopUp(_("Save game file name?"),
5175                   DefaultFileName(appData.oldSaveStyle ? "game" : "pgn"),
5176                   appData.oldSaveStyle ? ".game" : ".pgn",
5177                   SaveGame, "a");
5178 }
5179
5180 void
5181 SavePositionProc ()
5182 {
5183     FileNamePopUp(_("Save position file name?"),
5184                   DefaultFileName(appData.oldSaveStyle ? "pos" : "fen"),
5185                   appData.oldSaveStyle ? ".pos" : ".fen",
5186                   SavePosition, "a");
5187 }
5188
5189 void
5190 ReloadCmailMsgProc ()
5191 {
5192     ReloadCmailMsgEvent(FALSE);
5193 }
5194
5195 /* this variable is shared between CopyPositionProc and SendPositionSelection */
5196 char *selected_fen_position=NULL;
5197
5198 Boolean
5199 SendPositionSelection (Widget w, Atom *selection, Atom *target,
5200                        Atom *type_return, XtPointer *value_return,
5201                        unsigned long *length_return, int *format_return)
5202 {
5203   char *selection_tmp;
5204
5205   if (!selected_fen_position) return False; /* should never happen */
5206   if (*target == XA_STRING || *target == XA_UTF8_STRING(xDisplay)){
5207     /* note: since no XtSelectionDoneProc was registered, Xt will
5208      * automatically call XtFree on the value returned.  So have to
5209      * make a copy of it allocated with XtMalloc */
5210     selection_tmp= XtMalloc(strlen(selected_fen_position)+16);
5211     safeStrCpy(selection_tmp, selected_fen_position, strlen(selected_fen_position)+16 );
5212
5213     *value_return=selection_tmp;
5214     *length_return=strlen(selection_tmp);
5215     *type_return=*target;
5216     *format_return = 8; /* bits per byte */
5217     return True;
5218   } else if (*target == XA_TARGETS(xDisplay)) {
5219     Atom *targets_tmp = (Atom *) XtMalloc(2 * sizeof(Atom));
5220     targets_tmp[0] = XA_UTF8_STRING(xDisplay);
5221     targets_tmp[1] = XA_STRING;
5222     *value_return = targets_tmp;
5223     *type_return = XA_ATOM;
5224     *length_return = 2;
5225 #if 0
5226     // This code leads to a read of value_return out of bounds on 64-bit systems.
5227     // Other code which I have seen always sets *format_return to 32 independent of
5228     // sizeof(Atom) without adjusting *length_return. For instance see TextConvertSelection()
5229     // at http://cgit.freedesktop.org/xorg/lib/libXaw/tree/src/Text.c -- BJ
5230     *format_return = 8 * sizeof(Atom);
5231     if (*format_return > 32) {
5232       *length_return *= *format_return / 32;
5233       *format_return = 32;
5234     }
5235 #else
5236     *format_return = 32;
5237 #endif
5238     return True;
5239   } else {
5240     return False;
5241   }
5242 }
5243
5244 /* note: when called from menu all parameters are NULL, so no clue what the
5245  * Widget which was clicked on was, or what the click event was
5246  */
5247 void
5248 CopyPositionProc ()
5249 {
5250     /*
5251      * Set both PRIMARY (the selection) and CLIPBOARD, since we don't
5252      * have a notion of a position that is selected but not copied.
5253      * See http://www.freedesktop.org/wiki/Specifications/ClipboardsWiki
5254      */
5255     if(gameMode == EditPosition) EditPositionDone(TRUE);
5256     if (selected_fen_position) free(selected_fen_position);
5257     selected_fen_position = (char *)PositionToFEN(currentMove, NULL);
5258     if (!selected_fen_position) return;
5259     XtOwnSelection(menuBarWidget, XA_PRIMARY,
5260                    CurrentTime,
5261                    SendPositionSelection,
5262                    NULL/* lose_ownership_proc */ ,
5263                    NULL/* transfer_done_proc */);
5264     XtOwnSelection(menuBarWidget, XA_CLIPBOARD(xDisplay),
5265                    CurrentTime,
5266                    SendPositionSelection,
5267                    NULL/* lose_ownership_proc */ ,
5268                    NULL/* transfer_done_proc */);
5269 }
5270
5271 void
5272 CopyFENToClipboard ()
5273 { // wrapper to make call from back-end possible
5274   CopyPositionProc();
5275 }
5276
5277 /* function called when the data to Paste is ready */
5278 static void
5279 PastePositionCB (Widget w, XtPointer client_data, Atom *selection,
5280                  Atom *type, XtPointer value, unsigned long *len, int *format)
5281 {
5282   char *fenstr=value;
5283   if (value==NULL || *len==0) return; /* nothing had been selected to copy */
5284   fenstr[*len]='\0'; /* normally this string is terminated, but be safe */
5285   EditPositionPasteFEN(fenstr);
5286   XtFree(value);
5287 }
5288
5289 /* called when Paste Position button is pressed,
5290  * all parameters will be NULL */
5291 void
5292 PastePositionProc ()
5293 {
5294     XtGetSelectionValue(menuBarWidget,
5295       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
5296       /* (XtSelectionCallbackProc) */ PastePositionCB,
5297       NULL, /* client_data passed to PastePositionCB */
5298
5299       /* better to use the time field from the event that triggered the
5300        * call to this function, but that isn't trivial to get
5301        */
5302       CurrentTime
5303     );
5304     return;
5305 }
5306
5307 static Boolean
5308 SendGameSelection (Widget w, Atom *selection, Atom *target,
5309                    Atom *type_return, XtPointer *value_return,
5310                    unsigned long *length_return, int *format_return)
5311 {
5312   char *selection_tmp;
5313
5314   if (*target == XA_STRING || *target == XA_UTF8_STRING(xDisplay)){
5315     FILE* f = fopen(gameCopyFilename, "r");
5316     long len;
5317     size_t count;
5318     if (f == NULL) return False;
5319     fseek(f, 0, 2);
5320     len = ftell(f);
5321     rewind(f);
5322     selection_tmp = XtMalloc(len + 1);
5323     count = fread(selection_tmp, 1, len, f);
5324     fclose(f);
5325     if (len != count) {
5326       XtFree(selection_tmp);
5327       return False;
5328     }
5329     selection_tmp[len] = NULLCHAR;
5330     *value_return = selection_tmp;
5331     *length_return = len;
5332     *type_return = *target;
5333     *format_return = 8; /* bits per byte */
5334     return True;
5335   } else if (*target == XA_TARGETS(xDisplay)) {
5336     Atom *targets_tmp = (Atom *) XtMalloc(2 * sizeof(Atom));
5337     targets_tmp[0] = XA_UTF8_STRING(xDisplay);
5338     targets_tmp[1] = XA_STRING;
5339     *value_return = targets_tmp;
5340     *type_return = XA_ATOM;
5341     *length_return = 2;
5342 #if 0
5343     // This code leads to a read of value_return out of bounds on 64-bit systems.
5344     // Other code which I have seen always sets *format_return to 32 independent of
5345     // sizeof(Atom) without adjusting *length_return. For instance see TextConvertSelection()
5346     // at http://cgit.freedesktop.org/xorg/lib/libXaw/tree/src/Text.c -- BJ
5347     *format_return = 8 * sizeof(Atom);
5348     if (*format_return > 32) {
5349       *length_return *= *format_return / 32;
5350       *format_return = 32;
5351     }
5352 #else
5353     *format_return = 32;
5354 #endif
5355     return True;
5356   } else {
5357     return False;
5358   }
5359 }
5360
5361 void
5362 CopySomething ()
5363 {
5364   /*
5365    * Set both PRIMARY (the selection) and CLIPBOARD, since we don't
5366    * have a notion of a game that is selected but not copied.
5367    * See http://www.freedesktop.org/wiki/Specifications/ClipboardsWiki
5368    */
5369   XtOwnSelection(menuBarWidget, XA_PRIMARY,
5370                  CurrentTime,
5371                  SendGameSelection,
5372                  NULL/* lose_ownership_proc */ ,
5373                  NULL/* transfer_done_proc */);
5374   XtOwnSelection(menuBarWidget, XA_CLIPBOARD(xDisplay),
5375                  CurrentTime,
5376                  SendGameSelection,
5377                  NULL/* lose_ownership_proc */ ,
5378                  NULL/* transfer_done_proc */);
5379 }
5380
5381 /* note: when called from menu all parameters are NULL, so no clue what the
5382  * Widget which was clicked on was, or what the click event was
5383  */
5384 void
5385 CopyGameProc ()
5386 {
5387   int ret;
5388
5389   ret = SaveGameToFile(gameCopyFilename, FALSE);
5390   if (!ret) return;
5391
5392   CopySomething();
5393 }
5394
5395 void
5396 CopyGameListProc ()
5397 {
5398   if(!SaveGameListAsText(fopen(gameCopyFilename, "w"))) return;
5399   CopySomething();
5400 }
5401
5402 /* function called when the data to Paste is ready */
5403 static void
5404 PasteGameCB (Widget w, XtPointer client_data, Atom *selection,
5405              Atom *type, XtPointer value, unsigned long *len, int *format)
5406 {
5407   FILE* f;
5408   if (value == NULL || *len == 0) {
5409     return; /* nothing had been selected to copy */
5410   }
5411   f = fopen(gamePasteFilename, "w");
5412   if (f == NULL) {
5413     DisplayError(_("Can't open temp file"), errno);
5414     return;
5415   }
5416   fwrite(value, 1, *len, f);
5417   fclose(f);
5418   XtFree(value);
5419   LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
5420 }
5421
5422 /* called when Paste Game button is pressed,
5423  * all parameters will be NULL */
5424 void
5425 PasteGameProc ()
5426 {
5427     XtGetSelectionValue(menuBarWidget,
5428       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
5429       /* (XtSelectionCallbackProc) */ PasteGameCB,
5430       NULL, /* client_data passed to PasteGameCB */
5431
5432       /* better to use the time field from the event that triggered the
5433        * call to this function, but that isn't trivial to get
5434        */
5435       CurrentTime
5436     );
5437     return;
5438 }
5439
5440
5441 void
5442 AutoSaveGame ()
5443 {
5444     SaveGameProc();
5445 }
5446
5447
5448 void
5449 QuitProc ()
5450 {
5451     ExitEvent(0);
5452 }
5453
5454 void
5455 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
5456 {
5457     QuitProc();
5458 }
5459
5460 void
5461 AnalyzeModeProc ()
5462 {
5463     char buf[MSG_SIZ];
5464
5465     if (!first.analysisSupport) {
5466       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
5467       DisplayError(buf, 0);
5468       return;
5469     }
5470     /* [DM] icsEngineAnalyze [HGM] This is horrible code; reverse the gameMode and isEngineAnalyze tests! */
5471     if (appData.icsActive) {
5472         if (gameMode != IcsObserving) {
5473           snprintf(buf, MSG_SIZ, _("You are not observing a game"));
5474             DisplayError(buf, 0);
5475             /* secure check */
5476             if (appData.icsEngineAnalyze) {
5477                 if (appData.debugMode)
5478                     fprintf(debugFP, _("Found unexpected active ICS engine analyze \n"));
5479                 ExitAnalyzeMode();
5480                 ModeHighlight();
5481             }
5482             return;
5483         }
5484         /* if enable, use want disable icsEngineAnalyze */
5485         if (appData.icsEngineAnalyze) {
5486                 ExitAnalyzeMode();
5487                 ModeHighlight();
5488                 return;
5489         }
5490         appData.icsEngineAnalyze = TRUE;
5491         if (appData.debugMode)
5492             fprintf(debugFP, _("ICS engine analyze starting... \n"));
5493     }
5494 #ifndef OPTIONSDIALOG
5495     if (!appData.showThinking)
5496       ShowThinkingProc();
5497 #endif
5498
5499     AnalyzeModeEvent();
5500 }
5501
5502 void
5503 AnalyzeFileProc ()
5504 {
5505     if (!first.analysisSupport) {
5506       char buf[MSG_SIZ];
5507       snprintf(buf, sizeof(buf), _("%s does not support analysis"), first.tidy);
5508       DisplayError(buf, 0);
5509       return;
5510     }
5511 //    Reset(FALSE, TRUE);
5512 #ifndef OPTIONSDIALOG
5513     if (!appData.showThinking)
5514       ShowThinkingProc();
5515 #endif
5516     AnalyzeFileEvent();
5517 //    FileNamePopUp(_("File to analyze"), "", ".pgn .game", LoadGamePopUp, "rb");
5518     AnalysisPeriodicEvent(1);
5519 }
5520
5521 void
5522 MatchProc ()
5523 {
5524     MatchEvent(2);
5525 }
5526
5527 void
5528 EditCommentProc ()
5529 {
5530     Arg args[5];
5531     int j;
5532     if (PopDown(1)) { // popdown succesful
5533         MarkMenuItem("Edit Comment", False);
5534         MarkMenuItem("Show Comments", False);
5535     } else // was not up
5536         EditCommentEvent();
5537 }
5538
5539 void
5540 IcsInputBoxProc ()
5541 {
5542     if (!PopDown(4)) ICSInputBoxPopUp();
5543 }
5544
5545 void
5546 AdjuWhiteProc ()
5547 {
5548     UserAdjudicationEvent(+1);
5549 }
5550
5551 void
5552 AdjuBlackProc ()
5553 {
5554     UserAdjudicationEvent(-1);
5555 }
5556
5557 void
5558 AdjuDrawProc ()
5559 {
5560     UserAdjudicationEvent(0);
5561 }
5562
5563 void
5564 EnterKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
5565 {
5566     if (shellUp[4] == True)
5567       ICSInputSendText();
5568 }
5569
5570 void
5571 UpKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
5572 {   // [HGM] input: let up-arrow recall previous line from history
5573     Widget edit;
5574     int j;
5575     Arg args[16];
5576     String val;
5577     XawTextBlock t;
5578
5579     if (!shellUp[4]) return;
5580     edit = boxOptions[0].handle;
5581     j = 0;
5582     XtSetArg(args[j], XtNstring, &val); j++;
5583     XtGetValues(edit, args, j);
5584     val = PrevInHistory(val);
5585     XtCallActionProc(edit, "select-all", NULL, NULL, 0);
5586     XtCallActionProc(edit, "kill-selection", NULL, NULL, 0);
5587     if(val) {
5588         t.ptr = val; t.firstPos = 0; t.length = strlen(val); t.format = XawFmt8Bit;
5589         XawTextReplace(edit, 0, 0, &t);
5590         XawTextSetInsertionPoint(edit, 9999);
5591     }
5592 }
5593
5594 void
5595 DownKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
5596 {   // [HGM] input: let down-arrow recall next line from history
5597     Widget edit;
5598     String val;
5599     XawTextBlock t;
5600
5601     if (!shellUp[4]) return;
5602     edit = boxOptions[0].handle;
5603     val = NextInHistory();
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 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
5615 {
5616         if (!TempBackwardActive) {
5617                 TempBackwardActive = True;
5618                 BackwardEvent();
5619         }
5620 }
5621
5622 void
5623 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
5624 {
5625         /* Check to see if triggered by a key release event for a repeating key.
5626          * If so the next queued event will be a key press of the same key at the same time */
5627         if (XEventsQueued(xDisplay, QueuedAfterReading)) {
5628                 XEvent next;
5629                 XPeekEvent(xDisplay, &next);
5630                 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
5631                         next.xkey.keycode == event->xkey.keycode)
5632                                 return;
5633         }
5634     ForwardEvent();
5635         TempBackwardActive = False;
5636 }
5637
5638 void
5639 RevertProc ()
5640 {
5641     RevertEvent(False);
5642 }
5643
5644 void
5645 AnnotateProc ()
5646 {
5647     RevertEvent(True);
5648 }
5649
5650 void
5651 FlipViewProc ()
5652 {
5653     flipView = !flipView;
5654     DrawPosition(True, NULL);
5655 }
5656
5657 void
5658 PonderNextMoveProc ()
5659 {
5660     Arg args[16];
5661
5662     PonderNextMoveEvent(!appData.ponderNextMove);
5663 #ifndef OPTIONSDIALOG
5664     if (appData.ponderNextMove) {
5665         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5666     } else {
5667         XtSetArg(args[0], XtNleftBitmap, None);
5668     }
5669     XtSetValues(XtNameToWidget(menuBarWidget, "Ponder Next Move"),
5670                 args, 1);
5671 #endif
5672 }
5673
5674 #ifndef OPTIONSDIALOG
5675 void
5676 AlwaysQueenProc ()
5677 {
5678     Arg args[16];
5679
5680     appData.alwaysPromoteToQueen = !appData.alwaysPromoteToQueen;
5681
5682     if (appData.alwaysPromoteToQueen) {
5683         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5684     } else {
5685         XtSetArg(args[0], XtNleftBitmap, None);
5686     }
5687     XtSetValues(XtNameToWidget(menuBarWidget, "Always Queen"),
5688                 args, 1);
5689 }
5690
5691 void
5692 AnimateDraggingProc ()
5693 {
5694     Arg args[16];
5695
5696     appData.animateDragging = !appData.animateDragging;
5697
5698     if (appData.animateDragging) {
5699         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5700         CreateAnimVars();
5701     } else {
5702         XtSetArg(args[0], XtNleftBitmap, None);
5703     }
5704     XtSetValues(XtNameToWidget(menuBarWidget, "Animate Dragging"),
5705                 args, 1);
5706 }
5707
5708 void
5709 AnimateMovingProc ()
5710 {
5711     Arg args[16];
5712
5713     appData.animate = !appData.animate;
5714
5715     if (appData.animate) {
5716         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5717         CreateAnimVars();
5718     } else {
5719         XtSetArg(args[0], XtNleftBitmap, None);
5720     }
5721     XtSetValues(XtNameToWidget(menuBarWidget, "Animate Moving"),
5722                 args, 1);
5723 }
5724
5725 void
5726 AutoflagProc ()
5727 {
5728     Arg args[16];
5729
5730     appData.autoCallFlag = !appData.autoCallFlag;
5731
5732     if (appData.autoCallFlag) {
5733         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5734     } else {
5735         XtSetArg(args[0], XtNleftBitmap, None);
5736     }
5737     XtSetValues(XtNameToWidget(menuBarWidget, "Auto Flag"),
5738                 args, 1);
5739 }
5740
5741 void
5742 AutoflipProc ()
5743 {
5744     Arg args[16];
5745
5746     appData.autoFlipView = !appData.autoFlipView;
5747
5748     if (appData.autoFlipView) {
5749         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5750     } else {
5751         XtSetArg(args[0], XtNleftBitmap, None);
5752     }
5753     XtSetValues(XtNameToWidget(menuBarWidget, "Auto Flip View"),
5754                 args, 1);
5755 }
5756
5757 void
5758 BlindfoldProc ()
5759 {
5760     Arg args[16];
5761
5762     appData.blindfold = !appData.blindfold;
5763
5764     if (appData.blindfold) {
5765         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5766     } else {
5767         XtSetArg(args[0], XtNleftBitmap, None);
5768     }
5769     XtSetValues(XtNameToWidget(menuBarWidget, "Blindfold"),
5770                 args, 1);
5771
5772     DrawPosition(True, NULL);
5773 }
5774
5775 void
5776 TestLegalityProc ()
5777 {
5778     Arg args[16];
5779
5780     appData.testLegality = !appData.testLegality;
5781
5782     if (appData.testLegality) {
5783         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5784     } else {
5785         XtSetArg(args[0], XtNleftBitmap, None);
5786     }
5787     XtSetValues(XtNameToWidget(menuBarWidget, "Test Legality"),
5788                 args, 1);
5789 }
5790
5791
5792 void
5793 FlashMovesProc ()
5794 {
5795     Arg args[16];
5796
5797     if (appData.flashCount == 0) {
5798         appData.flashCount = 3;
5799     } else {
5800         appData.flashCount = -appData.flashCount;
5801     }
5802
5803     if (appData.flashCount > 0) {
5804         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5805     } else {
5806         XtSetArg(args[0], XtNleftBitmap, None);
5807     }
5808     XtSetValues(XtNameToWidget(menuBarWidget, "Flash Moves"),
5809                 args, 1);
5810 }
5811
5812 #if HIGHDRAG
5813 void
5814 HighlightDraggingProc ()
5815 {
5816     Arg args[16];
5817
5818     appData.highlightDragging = !appData.highlightDragging;
5819
5820     if (appData.highlightDragging) {
5821         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5822     } else {
5823         XtSetArg(args[0], XtNleftBitmap, None);
5824     }
5825     XtSetValues(XtNameToWidget(menuBarWidget,
5826                                "Highlight Dragging"), args, 1);
5827 }
5828 #endif
5829
5830 void
5831 HighlightLastMoveProc ()
5832 {
5833     Arg args[16];
5834
5835     appData.highlightLastMove = !appData.highlightLastMove;
5836
5837     if (appData.highlightLastMove) {
5838         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5839     } else {
5840         XtSetArg(args[0], XtNleftBitmap, None);
5841     }
5842     XtSetValues(XtNameToWidget(menuBarWidget,
5843                                "Highlight Last Move"), args, 1);
5844 }
5845
5846 void
5847 HighlightArrowProc ()
5848 {
5849     Arg args[16];
5850
5851     appData.highlightMoveWithArrow = !appData.highlightMoveWithArrow;
5852
5853     if (appData.highlightMoveWithArrow) {
5854         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5855     } else {
5856         XtSetArg(args[0], XtNleftBitmap, None);
5857     }
5858     XtSetValues(XtNameToWidget(menuBarWidget,
5859                                "Arrow"), args, 1);
5860 }
5861
5862 #if 0
5863 void
5864 IcsAlarmProc ()
5865 {
5866     Arg args[16];
5867
5868     appData.icsAlarm = !appData.icsAlarm;
5869
5870     if (appData.icsAlarm) {
5871         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5872     } else {
5873         XtSetArg(args[0], XtNleftBitmap, None);
5874     }
5875     XtSetValues(XtNameToWidget(menuBarWidget,
5876                                "ICS Alarm"), args, 1);
5877 }
5878 #endif
5879
5880 void
5881 MoveSoundProc ()
5882 {
5883     Arg args[16];
5884
5885     appData.ringBellAfterMoves = !appData.ringBellAfterMoves;
5886
5887     if (appData.ringBellAfterMoves) {
5888         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5889     } else {
5890         XtSetArg(args[0], XtNleftBitmap, None);
5891     }
5892     XtSetValues(XtNameToWidget(menuBarWidget, "Move Sound"),
5893                 args, 1);
5894 }
5895
5896 void
5897 OneClickProc ()
5898 {
5899     Arg args[16];
5900
5901     appData.oneClick = !appData.oneClick;
5902
5903     if (appData.oneClick) {
5904         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5905     } else {
5906         XtSetArg(args[0], XtNleftBitmap, None);
5907     }
5908     XtSetValues(XtNameToWidget(menuBarWidget, "OneClick"),
5909                 args, 1);
5910 }
5911
5912 void
5913 PeriodicUpdatesProc ()
5914 {
5915     Arg args[16];
5916
5917     PeriodicUpdatesEvent(!appData.periodicUpdates);
5918
5919     if (appData.periodicUpdates) {
5920         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5921     } else {
5922         XtSetArg(args[0], XtNleftBitmap, None);
5923     }
5924     XtSetValues(XtNameToWidget(menuBarWidget, "Periodic Updates"),
5925                 args, 1);
5926 }
5927
5928 void
5929 PopupExitMessageProc ()
5930 {
5931     Arg args[16];
5932
5933     appData.popupExitMessage = !appData.popupExitMessage;
5934
5935     if (appData.popupExitMessage) {
5936         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5937     } else {
5938         XtSetArg(args[0], XtNleftBitmap, None);
5939     }
5940     XtSetValues(XtNameToWidget(menuBarWidget,
5941                                "Popup Exit Message"), args, 1);
5942 }
5943
5944 void
5945 PopupMoveErrorsProc ()
5946 {
5947     Arg args[16];
5948
5949     appData.popupMoveErrors = !appData.popupMoveErrors;
5950
5951     if (appData.popupMoveErrors) {
5952         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5953     } else {
5954         XtSetArg(args[0], XtNleftBitmap, None);
5955     }
5956     XtSetValues(XtNameToWidget(menuBarWidget, "Popup Move Errors"),
5957                 args, 1);
5958 }
5959
5960 #if 0
5961 void
5962 PremoveProc ()
5963 {
5964     Arg args[16];
5965
5966     appData.premove = !appData.premove;
5967
5968     if (appData.premove) {
5969         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5970     } else {
5971         XtSetArg(args[0], XtNleftBitmap, None);
5972     }
5973     XtSetValues(XtNameToWidget(menuBarWidget,
5974                                "Premove"), args, 1);
5975 }
5976 #endif
5977
5978 void
5979 ShowCoordsProc ()
5980 {
5981     Arg args[16];
5982
5983     appData.showCoords = !appData.showCoords;
5984
5985     if (appData.showCoords) {
5986         XtSetArg(args[0], XtNleftBitmap, xMarkPixmap);
5987     } else {
5988         XtSetArg(args[0], XtNleftBitmap, None);
5989     }
5990     XtSetValues(XtNameToWidget(menuBarWidget, "Show Coords"),
5991                 args, 1);
5992
5993     DrawPosition(True, NULL);
5994 }
5995
5996 void
5997 ShowThinkingProc ()
5998 {
5999     appData.showThinking = !appData.showThinking; // [HGM] thinking: tken out of ShowThinkingEvent
6000     ShowThinkingEvent();
6001 }
6002
6003 void
6004 HideThinkingProc ()
6005 {
6006     Arg args[16];
6007
6008     appData.hideThinkingFromHuman = !appData.hideThinkingFromHuman; // [HGM] thinking: tken out of ShowThinkingEvent
6009     ShowThinkingEvent();
6010
6011     MarkMenuItem("Hide Thinking", appData.hideThinkingFromHuman);
6012 }
6013 #endif
6014
6015 void
6016 SaveOnExitProc ()
6017 {
6018     Arg args[16];
6019
6020     saveSettingsOnExit = !saveSettingsOnExit;
6021
6022     MarkMenuItem("Save Settings on Exit", saveSettingsOnExit);
6023 }
6024
6025 void
6026 SaveSettingsProc ()
6027 {
6028      SaveSettings(settingsFileName);
6029 }
6030
6031 void
6032 InfoProc ()
6033 {
6034     char buf[MSG_SIZ];
6035     snprintf(buf, sizeof(buf), "xterm -e info --directory %s --directory . -f %s &",
6036             INFODIR, INFOFILE);
6037     system(buf);
6038 }
6039
6040 void
6041 ManInner (Widget w, XEvent *event, String *prms, Cardinal *nprms)
6042 {   // called as key binding
6043     char buf[MSG_SIZ];
6044     String name;
6045     if (nprms && *nprms > 0)
6046       name = prms[0];
6047     else
6048       name = "xboard";
6049     snprintf(buf, sizeof(buf), "xterm -e man %s &", name);
6050     system(buf);
6051 }
6052
6053 void
6054 ManProc ()
6055 {   // called from menu
6056     ManInner(NULL, NULL, NULL, NULL);
6057 }
6058
6059 void
6060 BugReportProc ()
6061 {
6062     char buf[MSG_SIZ];
6063     snprintf(buf, MSG_SIZ, "%s mailto:bug-xboard@gnu.org", appData.sysOpen);
6064     system(buf);
6065 }
6066
6067 void
6068 GuideProc ()
6069 {
6070     char buf[MSG_SIZ];
6071     snprintf(buf, MSG_SIZ, "%s http://www.gnu.org/software/xboard/user_guide/UserGuide.html", appData.sysOpen);
6072     system(buf);
6073 }
6074
6075 void
6076 HomePageProc ()
6077 {
6078     char buf[MSG_SIZ];
6079     snprintf(buf, MSG_SIZ, "%s http://www.gnu.org/software/xboard/", appData.sysOpen);
6080     system(buf);
6081 }
6082
6083 void
6084 NewsPageProc ()
6085 {
6086     char buf[MSG_SIZ];
6087     snprintf(buf, MSG_SIZ, "%s http://www.gnu.org/software/xboard/whats_new/portal.html", appData.sysOpen);
6088     system(buf);
6089 }
6090
6091 void
6092 AboutProc ()
6093 {
6094     char buf[2 * MSG_SIZ];
6095 #if ZIPPY
6096     char *zippy = _(" (with Zippy code)");
6097 #else
6098     char *zippy = "";
6099 #endif
6100     snprintf(buf, sizeof(buf), 
6101 _("%s%s\n\n"
6102 "Copyright 1991 Digital Equipment Corporation\n"
6103 "Enhancements Copyright 1992-2012 Free Software Foundation\n"
6104 "Enhancements Copyright 2005 Alessandro Scotti\n\n"
6105 "%s is free software and carries NO WARRANTY;"
6106 "see the file COPYING for more information.\n\n"
6107 "Visit XBoard on the web at: http://www.gnu.org/software/xboard/\n"
6108 "Check out the newest features at: http://www.gnu.org/software/xboard/whats_new.html\n\n"
6109 "Report bugs via email at: <bug-xboard@gnu.org>\n\n"
6110   ),
6111             programVersion, zippy, PACKAGE);
6112     ErrorPopUp(_("About XBoard"), buf, FALSE);
6113 }
6114
6115 void
6116 DebugProc ()
6117 {
6118     appData.debugMode = !appData.debugMode;
6119 }
6120
6121 void
6122 NothingProc ()
6123 {
6124     return;
6125 }
6126
6127 void
6128 DisplayMessage (char *message, char *extMessage)
6129 {
6130   /* display a message in the message widget */
6131
6132   char buf[MSG_SIZ];
6133   Arg arg;
6134
6135   if (extMessage)
6136     {
6137       if (*message)
6138         {
6139           snprintf(buf, sizeof(buf), "%s  %s", message, extMessage);
6140           message = buf;
6141         }
6142       else
6143         {
6144           message = extMessage;
6145         };
6146     };
6147
6148     safeStrCpy(lastMsg, message, MSG_SIZ); // [HGM] make available
6149
6150   /* need to test if messageWidget already exists, since this function
6151      can also be called during the startup, if for example a Xresource
6152      is not set up correctly */
6153   if(messageWidget)
6154     {
6155       XtSetArg(arg, XtNlabel, message);
6156       XtSetValues(messageWidget, &arg, 1);
6157     };
6158
6159   return;
6160 }
6161
6162 void
6163 DisplayTitle (char *text)
6164 {
6165     Arg args[16];
6166     int i;
6167     char title[MSG_SIZ];
6168     char icon[MSG_SIZ];
6169
6170     if (text == NULL) text = "";
6171
6172     if (appData.titleInWindow) {
6173         i = 0;
6174         XtSetArg(args[i], XtNlabel, text);   i++;
6175         XtSetValues(titleWidget, args, i);
6176     }
6177
6178     if (*text != NULLCHAR) {
6179       safeStrCpy(icon, text, sizeof(icon)/sizeof(icon[0]) );
6180       safeStrCpy(title, text, sizeof(title)/sizeof(title[0]) );
6181     } else if (appData.icsActive) {
6182         snprintf(icon, sizeof(icon), "%s", appData.icsHost);
6183         snprintf(title, sizeof(title), "%s: %s", programName, appData.icsHost);
6184     } else if (appData.cmailGameName[0] != NULLCHAR) {
6185         snprintf(icon, sizeof(icon), "%s", "CMail");
6186         snprintf(title,sizeof(title), "%s: %s", programName, "CMail");
6187 #ifdef GOTHIC
6188     // [HGM] license: This stuff should really be done in back-end, but WinBoard already had a pop-up for it
6189     } else if (gameInfo.variant == VariantGothic) {
6190       safeStrCpy(icon,  programName, sizeof(icon)/sizeof(icon[0]) );
6191       safeStrCpy(title, GOTHIC,     sizeof(title)/sizeof(title[0]) );
6192 #endif
6193 #ifdef FALCON
6194     } else if (gameInfo.variant == VariantFalcon) {
6195       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
6196       safeStrCpy(title, FALCON, sizeof(title)/sizeof(title[0]) );
6197 #endif
6198     } else if (appData.noChessProgram) {
6199       safeStrCpy(icon, programName, sizeof(icon)/sizeof(icon[0]) );
6200       safeStrCpy(title, programName, sizeof(title)/sizeof(title[0]) );
6201     } else {
6202       safeStrCpy(icon, first.tidy, sizeof(icon)/sizeof(icon[0]) );
6203         snprintf(title,sizeof(title), "%s: %s", programName, first.tidy);
6204     }
6205     i = 0;
6206     XtSetArg(args[i], XtNiconName, (XtArgVal) icon);    i++;
6207     XtSetArg(args[i], XtNtitle, (XtArgVal) title);      i++;
6208     XtSetValues(shellWidget, args, i);
6209     XSync(xDisplay, False);
6210 }
6211
6212
6213 void
6214 DisplayError (String message, int error)
6215 {
6216     char buf[MSG_SIZ];
6217
6218     if (error == 0) {
6219         if (appData.debugMode || appData.matchMode) {
6220             fprintf(stderr, "%s: %s\n", programName, message);
6221         }
6222     } else {
6223         if (appData.debugMode || appData.matchMode) {
6224             fprintf(stderr, "%s: %s: %s\n",
6225                     programName, message, strerror(error));
6226         }
6227         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
6228         message = buf;
6229     }
6230     ErrorPopUp(_("Error"), message, FALSE);
6231 }
6232
6233
6234 void
6235 DisplayMoveError (String message)
6236 {
6237     fromX = fromY = -1;
6238     ClearHighlights();
6239     DrawPosition(FALSE, NULL);
6240     if (appData.debugMode || appData.matchMode) {
6241         fprintf(stderr, "%s: %s\n", programName, message);
6242     }
6243     if (appData.popupMoveErrors) {
6244         ErrorPopUp(_("Error"), message, FALSE);
6245     } else {
6246         DisplayMessage(message, "");
6247     }
6248 }
6249
6250
6251 void
6252 DisplayFatalError (String message, int error, int status)
6253 {
6254     char buf[MSG_SIZ];
6255
6256     errorExitStatus = status;
6257     if (error == 0) {
6258         fprintf(stderr, "%s: %s\n", programName, message);
6259     } else {
6260         fprintf(stderr, "%s: %s: %s\n",
6261                 programName, message, strerror(error));
6262         snprintf(buf, sizeof(buf), "%s: %s", message, strerror(error));
6263         message = buf;
6264     }
6265     if (appData.popupExitMessage && boardWidget && XtIsRealized(boardWidget)) {
6266       ErrorPopUp(status ? _("Fatal Error") : _("Exiting"), message, TRUE);
6267     } else {
6268       ExitEvent(status);
6269     }
6270 }
6271
6272 void
6273 DisplayInformation (String message)
6274 {
6275     ErrorPopDown();
6276     ErrorPopUp(_("Information"), message, TRUE);
6277 }
6278
6279 void
6280 DisplayNote (String message)
6281 {
6282     ErrorPopDown();
6283     ErrorPopUp(_("Note"), message, FALSE);
6284 }
6285
6286 static int
6287 NullXErrorCheck (Display *dpy, XErrorEvent *error_event)
6288 {
6289     return 0;
6290 }
6291
6292 void
6293 DisplayIcsInteractionTitle (String message)
6294 {
6295   if (oldICSInteractionTitle == NULL) {
6296     /* Magic to find the old window title, adapted from vim */
6297     char *wina = getenv("WINDOWID");
6298     if (wina != NULL) {
6299       Window win = (Window) atoi(wina);
6300       Window root, parent, *children;
6301       unsigned int nchildren;
6302       int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
6303       for (;;) {
6304         if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
6305         if (!XQueryTree(xDisplay, win, &root, &parent,
6306                         &children, &nchildren)) break;
6307         if (children) XFree((void *)children);
6308         if (parent == root || parent == 0) break;
6309         win = parent;
6310       }
6311       XSetErrorHandler(oldHandler);
6312     }
6313     if (oldICSInteractionTitle == NULL) {
6314       oldICSInteractionTitle = "xterm";
6315     }
6316   }
6317   printf("\033]0;%s\007", message);
6318   fflush(stdout);
6319 }
6320
6321 char pendingReplyPrefix[MSG_SIZ];
6322 ProcRef pendingReplyPR;
6323
6324 void
6325 AskQuestionProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
6326 {
6327     if (*nprms != 4) {
6328         fprintf(stderr, _("AskQuestionProc needed 4 parameters, got %d\n"),
6329                 *nprms);
6330         return;
6331     }
6332     AskQuestionEvent(prms[0], prms[1], prms[2], prms[3]);
6333 }
6334
6335 void
6336 AskQuestionPopDown ()
6337 {
6338     if (!askQuestionUp) return;
6339     XtPopdown(askQuestionShell);
6340     XtDestroyWidget(askQuestionShell);
6341     askQuestionUp = False;
6342 }
6343
6344 void
6345 AskQuestionReplyAction (Widget w, XEvent *event, String *prms, Cardinal *nprms)
6346 {
6347     char buf[MSG_SIZ];
6348     int err;
6349     String reply;
6350
6351     reply = XawDialogGetValueString(w = XtParent(w));
6352     safeStrCpy(buf, pendingReplyPrefix, sizeof(buf)/sizeof(buf[0]) );
6353     if (*buf) strncat(buf, " ", MSG_SIZ - strlen(buf) - 1);
6354     strncat(buf, reply, MSG_SIZ - strlen(buf) - 1);
6355     strncat(buf, "\n",  MSG_SIZ - strlen(buf) - 1);
6356     OutputToProcess(pendingReplyPR, buf, strlen(buf), &err);
6357     AskQuestionPopDown();
6358
6359     if (err) DisplayFatalError(_("Error writing to chess program"), err, 0);
6360 }
6361
6362 void
6363 AskQuestionCallback (Widget w, XtPointer client_data, XtPointer call_data)
6364 {
6365     String name;
6366     Arg args[16];
6367
6368     XtSetArg(args[0], XtNlabel, &name);
6369     XtGetValues(w, args, 1);
6370
6371     if (strcmp(name, _("cancel")) == 0) {
6372         AskQuestionPopDown();
6373     } else {
6374         AskQuestionReplyAction(w, NULL, NULL, NULL);
6375     }
6376 }
6377
6378 void
6379 AskQuestion (char *title, char *question, char *replyPrefix, ProcRef pr)
6380 {
6381     Arg args[16];
6382     Widget popup, layout, dialog, edit;
6383     Window root, child;
6384     int x, y, i;
6385     int win_x, win_y;
6386     unsigned int mask;
6387
6388     safeStrCpy(pendingReplyPrefix, replyPrefix, sizeof(pendingReplyPrefix)/sizeof(pendingReplyPrefix[0]) );
6389     pendingReplyPR = pr;
6390
6391     i = 0;
6392     XtSetArg(args[i], XtNresizable, True); i++;
6393     XtSetArg(args[i], XtNwidth, DIALOG_SIZE); i++;
6394     askQuestionShell = popup =
6395       XtCreatePopupShell(title, transientShellWidgetClass,
6396                          shellWidget, args, i);
6397
6398     layout =
6399       XtCreateManagedWidget(layoutName, formWidgetClass, popup,
6400                             layoutArgs, XtNumber(layoutArgs));
6401
6402     i = 0;
6403     XtSetArg(args[i], XtNlabel, question); i++;
6404     XtSetArg(args[i], XtNvalue, ""); i++;
6405     XtSetArg(args[i], XtNborderWidth, 0); i++;
6406     dialog = XtCreateManagedWidget("question", dialogWidgetClass,
6407                                    layout, args, i);
6408
6409     XawDialogAddButton(dialog, _("enter"), AskQuestionCallback,
6410                        (XtPointer) dialog);
6411     XawDialogAddButton(dialog, _("cancel"), AskQuestionCallback,
6412                        (XtPointer) dialog);
6413
6414     XtRealizeWidget(popup);
6415     CatchDeleteWindow(popup, "AskQuestionPopDown");
6416
6417     XQueryPointer(xDisplay, xBoardWindow, &root, &child,
6418                   &x, &y, &win_x, &win_y, &mask);
6419
6420     XtSetArg(args[0], XtNx, x - 10);
6421     XtSetArg(args[1], XtNy, y - 30);
6422     XtSetValues(popup, args, 2);
6423
6424     XtPopup(popup, XtGrabExclusive);
6425     askQuestionUp = True;
6426
6427     edit = XtNameToWidget(dialog, "*value");
6428     XtSetKeyboardFocus(popup, edit);
6429 }
6430
6431
6432 void
6433 PlaySound (char *name)
6434 {
6435   if (*name == NULLCHAR) {
6436     return;
6437   } else if (strcmp(name, "$") == 0) {
6438     putc(BELLCHAR, stderr);
6439   } else {
6440     char buf[2048];
6441     char *prefix = "", *sep = "";
6442     if(appData.soundProgram[0] == NULLCHAR) return;
6443     if(!strchr(name, '/')) { prefix = appData.soundDirectory; sep = "/"; }
6444     snprintf(buf, sizeof(buf), "%s '%s%s%s' &", appData.soundProgram, prefix, sep, name);
6445     system(buf);
6446   }
6447 }
6448
6449 void
6450 RingBell ()
6451 {
6452   PlaySound(appData.soundMove);
6453 }
6454
6455 void
6456 PlayIcsWinSound ()
6457 {
6458   PlaySound(appData.soundIcsWin);
6459 }
6460
6461 void
6462 PlayIcsLossSound ()
6463 {
6464   PlaySound(appData.soundIcsLoss);
6465 }
6466
6467 void
6468 PlayIcsDrawSound ()
6469 {
6470   PlaySound(appData.soundIcsDraw);
6471 }
6472
6473 void
6474 PlayIcsUnfinishedSound ()
6475 {
6476   PlaySound(appData.soundIcsUnfinished);
6477 }
6478
6479 void
6480 PlayAlarmSound ()
6481 {
6482   PlaySound(appData.soundIcsAlarm);
6483 }
6484
6485 void
6486 PlayTellSound ()
6487 {
6488   PlaySound(appData.soundTell);
6489 }
6490
6491 void
6492 EchoOn ()
6493 {
6494     system("stty echo");
6495     noEcho = False;
6496 }
6497
6498 void
6499 EchoOff ()
6500 {
6501     system("stty -echo");
6502     noEcho = True;
6503 }
6504
6505 void
6506 RunCommand (char *buf)
6507 {
6508     system(buf);
6509 }
6510
6511 void
6512 Colorize (ColorClass cc, int continuation)
6513 {
6514     char buf[MSG_SIZ];
6515     int count, outCount, error;
6516
6517     if (textColors[(int)cc].bg > 0) {
6518         if (textColors[(int)cc].fg > 0) {
6519           snprintf(buf, MSG_SIZ, "\033[0;%d;%d;%dm", textColors[(int)cc].attr,
6520                    textColors[(int)cc].fg, textColors[(int)cc].bg);
6521         } else {
6522           snprintf(buf, MSG_SIZ, "\033[0;%d;%dm", textColors[(int)cc].attr,
6523                    textColors[(int)cc].bg);
6524         }
6525     } else {
6526         if (textColors[(int)cc].fg > 0) {
6527           snprintf(buf, MSG_SIZ, "\033[0;%d;%dm", textColors[(int)cc].attr,
6528                     textColors[(int)cc].fg);
6529         } else {
6530           snprintf(buf, MSG_SIZ, "\033[0;%dm", textColors[(int)cc].attr);
6531         }
6532     }
6533     count = strlen(buf);
6534     outCount = OutputToProcess(NoProc, buf, count, &error);
6535     if (outCount < count) {
6536         DisplayFatalError(_("Error writing to display"), error, 1);
6537     }
6538
6539     if (continuation) return;
6540     switch (cc) {
6541     case ColorShout:
6542       PlaySound(appData.soundShout);
6543       break;
6544     case ColorSShout:
6545       PlaySound(appData.soundSShout);
6546       break;
6547     case ColorChannel1:
6548       PlaySound(appData.soundChannel1);
6549       break;
6550     case ColorChannel:
6551       PlaySound(appData.soundChannel);
6552       break;
6553     case ColorKibitz:
6554       PlaySound(appData.soundKibitz);
6555       break;
6556     case ColorTell:
6557       PlaySound(appData.soundTell);
6558       break;
6559     case ColorChallenge:
6560       PlaySound(appData.soundChallenge);
6561       break;
6562     case ColorRequest:
6563       PlaySound(appData.soundRequest);
6564       break;
6565     case ColorSeek:
6566       PlaySound(appData.soundSeek);
6567       break;
6568     case ColorNormal:
6569     case ColorNone:
6570     default:
6571       break;
6572     }
6573 }
6574
6575 char *
6576 UserName ()
6577 {
6578     return getpwuid(getuid())->pw_name;
6579 }
6580
6581 static char *
6582 ExpandPathName (char *path)
6583 {
6584     static char static_buf[4*MSG_SIZ];
6585     char *d, *s, buf[4*MSG_SIZ];
6586     struct passwd *pwd;
6587
6588     s = path;
6589     d = static_buf;
6590
6591     while (*s && isspace(*s))
6592       ++s;
6593
6594     if (!*s) {
6595         *d = 0;
6596         return static_buf;
6597     }
6598
6599     if (*s == '~') {
6600         if (*(s+1) == '/') {
6601           safeStrCpy(d, getpwuid(getuid())->pw_dir, 4*MSG_SIZ );
6602           strcat(d, s+1);
6603         }
6604         else {
6605           safeStrCpy(buf, s+1, sizeof(buf)/sizeof(buf[0]) );
6606           { char *p; if(p = strchr(buf, '/')) *p = 0; }
6607           pwd = getpwnam(buf);
6608           if (!pwd)
6609             {
6610               fprintf(stderr, _("ERROR: Unknown user %s (in path %s)\n"),
6611                       buf, path);
6612               return NULL;
6613             }
6614           safeStrCpy(d, pwd->pw_dir, 4*MSG_SIZ );
6615           strcat(d, strchr(s+1, '/'));
6616         }
6617     }
6618     else
6619       safeStrCpy(d, s, 4*MSG_SIZ );
6620
6621     return static_buf;
6622 }
6623
6624 char *
6625 HostName ()
6626 {
6627     static char host_name[MSG_SIZ];
6628
6629 #if HAVE_GETHOSTNAME
6630     gethostname(host_name, MSG_SIZ);
6631     return host_name;
6632 #else  /* not HAVE_GETHOSTNAME */
6633 # if HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H
6634     sysinfo(SI_HOSTNAME, host_name, MSG_SIZ);
6635     return host_name;
6636 # else /* not (HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H) */
6637     return "localhost";
6638 # endif/* not (HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H) */
6639 #endif /* not HAVE_GETHOSTNAME */
6640 }
6641
6642 XtIntervalId delayedEventTimerXID = 0;
6643 DelayedEventCallback delayedEventCallback = 0;
6644
6645 void
6646 FireDelayedEvent ()
6647 {
6648     delayedEventTimerXID = 0;
6649     delayedEventCallback();
6650 }
6651
6652 void
6653 ScheduleDelayedEvent (DelayedEventCallback cb, long millisec)
6654 {
6655     if(delayedEventTimerXID && delayedEventCallback == cb)
6656         // [HGM] alive: replace, rather than add or flush identical event
6657         XtRemoveTimeOut(delayedEventTimerXID);
6658     delayedEventCallback = cb;
6659     delayedEventTimerXID =
6660       XtAppAddTimeOut(appContext, millisec,
6661                       (XtTimerCallbackProc) FireDelayedEvent, (XtPointer) 0);
6662 }
6663
6664 DelayedEventCallback
6665 GetDelayedEvent ()
6666 {
6667   if (delayedEventTimerXID) {
6668     return delayedEventCallback;
6669   } else {
6670     return NULL;
6671   }
6672 }
6673
6674 void
6675 CancelDelayedEvent ()
6676 {
6677   if (delayedEventTimerXID) {
6678     XtRemoveTimeOut(delayedEventTimerXID);
6679     delayedEventTimerXID = 0;
6680   }
6681 }
6682
6683 XtIntervalId loadGameTimerXID = 0;
6684
6685 int
6686 LoadGameTimerRunning ()
6687 {
6688     return loadGameTimerXID != 0;
6689 }
6690
6691 int
6692 StopLoadGameTimer ()
6693 {
6694     if (loadGameTimerXID != 0) {
6695         XtRemoveTimeOut(loadGameTimerXID);
6696         loadGameTimerXID = 0;
6697         return TRUE;
6698     } else {
6699         return FALSE;
6700     }
6701 }
6702
6703 void
6704 LoadGameTimerCallback (XtPointer arg, XtIntervalId *id)
6705 {
6706     loadGameTimerXID = 0;
6707     AutoPlayGameLoop();
6708 }
6709
6710 void
6711 StartLoadGameTimer (long millisec)
6712 {
6713     loadGameTimerXID =
6714       XtAppAddTimeOut(appContext, millisec,
6715                       (XtTimerCallbackProc) LoadGameTimerCallback,
6716                       (XtPointer) 0);
6717 }
6718
6719 XtIntervalId analysisClockXID = 0;
6720
6721 void
6722 AnalysisClockCallback (XtPointer arg, XtIntervalId *id)
6723 {
6724     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
6725          || appData.icsEngineAnalyze) { // [DM]
6726         AnalysisPeriodicEvent(0);
6727         StartAnalysisClock();
6728     }
6729 }
6730
6731 void
6732 StartAnalysisClock ()
6733 {
6734     analysisClockXID =
6735       XtAppAddTimeOut(appContext, 2000,
6736                       (XtTimerCallbackProc) AnalysisClockCallback,
6737                       (XtPointer) 0);
6738 }
6739
6740 XtIntervalId clockTimerXID = 0;
6741
6742 int
6743 ClockTimerRunning ()
6744 {
6745     return clockTimerXID != 0;
6746 }
6747
6748 int
6749 StopClockTimer ()
6750 {
6751     if (clockTimerXID != 0) {
6752         XtRemoveTimeOut(clockTimerXID);
6753         clockTimerXID = 0;
6754         return TRUE;
6755     } else {
6756         return FALSE;
6757     }
6758 }
6759
6760 void
6761 ClockTimerCallback (XtPointer arg, XtIntervalId *id)
6762 {
6763     clockTimerXID = 0;
6764     DecrementClocks();
6765 }
6766
6767 void
6768 StartClockTimer (long millisec)
6769 {
6770     clockTimerXID =
6771       XtAppAddTimeOut(appContext, millisec,
6772                       (XtTimerCallbackProc) ClockTimerCallback,
6773                       (XtPointer) 0);
6774 }
6775
6776 void
6777 DisplayTimerLabel (Widget w, char *color, long timer, int highlight)
6778 {
6779     char buf[MSG_SIZ];
6780     Arg args[16];
6781
6782     /* check for low time warning */
6783     Pixel foregroundOrWarningColor = timerForegroundPixel;
6784
6785     if (timer > 0 &&
6786         appData.lowTimeWarning &&
6787         (timer / 1000) < appData.icsAlarmTime)
6788       foregroundOrWarningColor = lowTimeWarningColor;
6789
6790     if (appData.clockMode) {
6791       snprintf(buf, MSG_SIZ, "%s: %s", color, TimeString(timer));
6792       XtSetArg(args[0], XtNlabel, buf);
6793     } else {
6794       snprintf(buf, MSG_SIZ, "%s  ", color);
6795       XtSetArg(args[0], XtNlabel, buf);
6796     }
6797
6798     if (highlight) {
6799
6800         XtSetArg(args[1], XtNbackground, foregroundOrWarningColor);
6801         XtSetArg(args[2], XtNforeground, timerBackgroundPixel);
6802     } else {
6803         XtSetArg(args[1], XtNbackground, timerBackgroundPixel);
6804         XtSetArg(args[2], XtNforeground, foregroundOrWarningColor);
6805     }
6806
6807     XtSetValues(w, args, 3);
6808 }
6809
6810 void
6811 DisplayWhiteClock (long timeRemaining, int highlight)
6812 {
6813     Arg args[16];
6814
6815     if(appData.noGUI) return;
6816     DisplayTimerLabel(whiteTimerWidget, _("White"), timeRemaining, highlight);
6817     if (highlight && iconPixmap == bIconPixmap) {
6818         iconPixmap = wIconPixmap;
6819         XtSetArg(args[0], XtNiconPixmap, iconPixmap);
6820         XtSetValues(shellWidget, args, 1);
6821     }
6822 }
6823
6824 void
6825 DisplayBlackClock (long timeRemaining, int highlight)
6826 {
6827     Arg args[16];
6828
6829     if(appData.noGUI) return;
6830     DisplayTimerLabel(blackTimerWidget, _("Black"), timeRemaining, highlight);
6831     if (highlight && iconPixmap == wIconPixmap) {
6832         iconPixmap = bIconPixmap;
6833         XtSetArg(args[0], XtNiconPixmap, iconPixmap);
6834         XtSetValues(shellWidget, args, 1);
6835     }
6836 }
6837
6838 #define CPNone 0
6839 #define CPReal 1
6840 #define CPComm 2
6841 #define CPSock 3
6842 #define CPLoop 4
6843 typedef int CPKind;
6844
6845 typedef struct {
6846     CPKind kind;
6847     int pid;
6848     int fdTo, fdFrom;
6849 } ChildProc;
6850
6851
6852 int
6853 StartChildProcess (char *cmdLine, char *dir, ProcRef *pr)
6854 {
6855     char *argv[64], *p;
6856     int i, pid;
6857     int to_prog[2], from_prog[2];
6858     ChildProc *cp;
6859     char buf[MSG_SIZ];
6860
6861     if (appData.debugMode) {
6862         fprintf(debugFP, "StartChildProcess (dir=\"%s\") %s\n",dir, cmdLine);
6863     }
6864
6865     /* We do NOT feed the cmdLine to the shell; we just
6866        parse it into blank-separated arguments in the
6867        most simple-minded way possible.
6868        */
6869     i = 0;
6870     safeStrCpy(buf, cmdLine, sizeof(buf)/sizeof(buf[0]) );
6871     p = buf;
6872     for (;;) {
6873         while(*p == ' ') p++;
6874         argv[i++] = p;
6875         if(*p == '"' || *p == '\'')
6876              p = strchr(++argv[i-1], *p);
6877         else p = strchr(p, ' ');
6878         if (p == NULL) break;
6879         *p++ = NULLCHAR;
6880     }
6881     argv[i] = NULL;
6882
6883     SetUpChildIO(to_prog, from_prog);
6884
6885     if ((pid = fork()) == 0) {
6886         /* Child process */
6887         // [HGM] PSWBTM: made order resistant against case where fd of created pipe was 0 or 1
6888         close(to_prog[1]);     // first close the unused pipe ends
6889         close(from_prog[0]);
6890         dup2(to_prog[0], 0);   // to_prog was created first, nd is the only one to use 0 or 1
6891         dup2(from_prog[1], 1);
6892         if(to_prog[0] >= 2) close(to_prog[0]); // if 0 or 1, the dup2 already cosed the original
6893         close(from_prog[1]);                   // and closing again loses one of the pipes!
6894         if(fileno(stderr) >= 2) // better safe than sorry...
6895                 dup2(1, fileno(stderr)); /* force stderr to the pipe */
6896
6897         if (dir[0] != NULLCHAR && chdir(dir) != 0) {
6898             perror(dir);
6899             exit(1);
6900         }
6901
6902         nice(appData.niceEngines); // [HGM] nice: adjust priority of engine proc
6903
6904         execvp(argv[0], argv);
6905
6906         /* If we get here, exec failed */
6907         perror(argv[0]);
6908         exit(1);
6909     }
6910
6911     /* Parent process */
6912     close(to_prog[0]);
6913     close(from_prog[1]);
6914
6915     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
6916     cp->kind = CPReal;
6917     cp->pid = pid;
6918     cp->fdFrom = from_prog[0];
6919     cp->fdTo = to_prog[1];
6920     *pr = (ProcRef) cp;
6921     return 0;
6922 }
6923
6924 // [HGM] kill: implement the 'hard killing' of AS's Winboard_x
6925 static RETSIGTYPE
6926 AlarmCallBack (int n)
6927 {
6928     return;
6929 }
6930
6931 void
6932 DestroyChildProcess (ProcRef pr, int signalType)
6933 {
6934     ChildProc *cp = (ChildProc *) pr;
6935
6936     if (cp->kind != CPReal) return;
6937     cp->kind = CPNone;
6938     if (signalType == 10) { // [HGM] kill: if it does not terminate in 3 sec, kill
6939         signal(SIGALRM, AlarmCallBack);
6940         alarm(3);
6941         if(wait((int *) 0) == -1) { // process does not terminate on its own accord
6942             kill(cp->pid, SIGKILL); // kill it forcefully
6943             wait((int *) 0);        // and wait again
6944         }
6945     } else {
6946         if (signalType) {
6947             kill(cp->pid, signalType == 9 ? SIGKILL : SIGTERM); // [HGM] kill: use hard kill if so requested
6948         }
6949         /* Process is exiting either because of the kill or because of
6950            a quit command sent by the backend; either way, wait for it to die.
6951         */
6952         wait((int *) 0);
6953     }
6954     close(cp->fdFrom);
6955     close(cp->fdTo);
6956 }
6957
6958 void
6959 InterruptChildProcess (ProcRef pr)
6960 {
6961     ChildProc *cp = (ChildProc *) pr;
6962
6963     if (cp->kind != CPReal) return;
6964     (void) kill(cp->pid, SIGINT); /* stop it thinking */
6965 }
6966
6967 int
6968 OpenTelnet (char *host, char *port, ProcRef *pr)
6969 {
6970     char cmdLine[MSG_SIZ];
6971
6972     if (port[0] == NULLCHAR) {
6973       snprintf(cmdLine, sizeof(cmdLine), "%s %s", appData.telnetProgram, host);
6974     } else {
6975       snprintf(cmdLine, sizeof(cmdLine), "%s %s %s", appData.telnetProgram, host, port);
6976     }
6977     return StartChildProcess(cmdLine, "", pr);
6978 }
6979
6980 int
6981 OpenTCP (char *host, char *port, ProcRef *pr)
6982 {
6983 #if OMIT_SOCKETS
6984     DisplayFatalError(_("Socket support is not configured in"), 0, 2);
6985 #else  /* !OMIT_SOCKETS */
6986     struct addrinfo hints;
6987     struct addrinfo *ais, *ai;
6988     int error;
6989     int s=0;
6990     ChildProc *cp;
6991
6992     memset(&hints, 0, sizeof(hints));
6993     hints.ai_family = AF_UNSPEC;
6994     hints.ai_socktype = SOCK_STREAM;
6995
6996     error = getaddrinfo(host, port, &hints, &ais);
6997     if (error != 0) {
6998       /* a getaddrinfo error is not an errno, so can't return it */
6999       fprintf(debugFP, "getaddrinfo(%s, %s): %s\n",
7000               host, port, gai_strerror(error));
7001       return ENOENT;
7002     }
7003      
7004     for (ai = ais; ai != NULL; ai = ai->ai_next) {
7005       if ((s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0) {
7006         error = errno;
7007         continue;
7008       }
7009       if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) {
7010         error = errno;
7011         continue;
7012       }
7013       error = 0;
7014       break;
7015     }
7016     freeaddrinfo(ais);
7017
7018     if (error != 0) {
7019       return error;
7020     }
7021
7022     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
7023     cp->kind = CPSock;
7024     cp->pid = 0;
7025     cp->fdFrom = s;
7026     cp->fdTo = s;
7027     *pr = (ProcRef) cp;
7028 #endif /* !OMIT_SOCKETS */
7029
7030     return 0;
7031 }
7032
7033 int
7034 OpenCommPort (char *name, ProcRef *pr)
7035 {
7036     int fd;
7037     ChildProc *cp;
7038
7039     fd = open(name, 2, 0);
7040     if (fd < 0) return errno;
7041
7042     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
7043     cp->kind = CPComm;
7044     cp->pid = 0;
7045     cp->fdFrom = fd;
7046     cp->fdTo = fd;
7047     *pr = (ProcRef) cp;
7048
7049     return 0;
7050 }
7051
7052 int
7053 OpenLoopback (ProcRef *pr)
7054 {
7055     ChildProc *cp;
7056     int to[2], from[2];
7057
7058     SetUpChildIO(to, from);
7059
7060     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
7061     cp->kind = CPLoop;
7062     cp->pid = 0;
7063     cp->fdFrom = to[0];         /* note not from[0]; we are doing a loopback */
7064     cp->fdTo = to[1];
7065     *pr = (ProcRef) cp;
7066
7067     return 0;
7068 }
7069
7070 int
7071 OpenRcmd (char *host, char *user, char *cmd, ProcRef *pr)
7072 {
7073     DisplayFatalError(_("internal rcmd not implemented for Unix"), 0, 1);
7074     return -1;
7075 }
7076
7077 #define INPUT_SOURCE_BUF_SIZE 8192
7078
7079 typedef struct {
7080     CPKind kind;
7081     int fd;
7082     int lineByLine;
7083     char *unused;
7084     InputCallback func;
7085     XtInputId xid;
7086     char buf[INPUT_SOURCE_BUF_SIZE];
7087     VOIDSTAR closure;
7088 } InputSource;
7089
7090 void
7091 DoInputCallback (caddr_t closure, int *source, XtInputId *xid)
7092 {
7093     InputSource *is = (InputSource *) closure;
7094     int count;
7095     int error;
7096     char *p, *q;
7097
7098     if (is->lineByLine) {
7099         count = read(is->fd, is->unused,
7100                      INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
7101         if (count <= 0) {
7102             (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
7103             return;
7104         }
7105         is->unused += count;
7106         p = is->buf;
7107         while (p < is->unused) {
7108             q = memchr(p, '\n', is->unused - p);
7109             if (q == NULL) break;
7110             q++;
7111             (is->func)(is, is->closure, p, q - p, 0);
7112             p = q;
7113         }
7114         q = is->buf;
7115         while (p < is->unused) {
7116             *q++ = *p++;
7117         }
7118         is->unused = q;
7119     } else {
7120         count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
7121         if (count == -1)
7122           error = errno;
7123         else
7124           error = 0;
7125         (is->func)(is, is->closure, is->buf, count, error);
7126     }
7127 }
7128
7129 InputSourceRef
7130 AddInputSource (ProcRef pr, int lineByLine, InputCallback func, VOIDSTAR closure)
7131 {
7132     InputSource *is;
7133     ChildProc *cp = (ChildProc *) pr;
7134
7135     is = (InputSource *) calloc(1, sizeof(InputSource));
7136     is->lineByLine = lineByLine;
7137     is->func = func;
7138     if (pr == NoProc) {
7139         is->kind = CPReal;
7140         is->fd = fileno(stdin);
7141     } else {
7142         is->kind = cp->kind;
7143         is->fd = cp->fdFrom;
7144     }
7145     if (lineByLine) {
7146         is->unused = is->buf;
7147     }
7148
7149     is->xid = XtAppAddInput(appContext, is->fd,
7150                             (XtPointer) (XtInputReadMask),
7151                             (XtInputCallbackProc) DoInputCallback,
7152                             (XtPointer) is);
7153     is->closure = closure;
7154     return (InputSourceRef) is;
7155 }
7156
7157 void
7158 RemoveInputSource (InputSourceRef isr)
7159 {
7160     InputSource *is = (InputSource *) isr;
7161
7162     if (is->xid == 0) return;
7163     XtRemoveInput(is->xid);
7164     is->xid = 0;
7165 }
7166
7167 int
7168 OutputToProcess (ProcRef pr, char *message, int count, int *outError)
7169 {
7170     static int line = 0;
7171     ChildProc *cp = (ChildProc *) pr;
7172     int outCount;
7173
7174     if (pr == NoProc)
7175     {
7176         if (appData.noJoin || !appData.useInternalWrap)
7177             outCount = fwrite(message, 1, count, stdout);
7178         else
7179         {
7180             int width = get_term_width();
7181             int len = wrap(NULL, message, count, width, &line);
7182             char *msg = malloc(len);
7183             int dbgchk;
7184
7185             if (!msg)
7186                 outCount = fwrite(message, 1, count, stdout);
7187             else
7188             {
7189                 dbgchk = wrap(msg, message, count, width, &line);
7190                 if (dbgchk != len && appData.debugMode)
7191                     fprintf(debugFP, "wrap(): dbgchk(%d) != len(%d)\n", dbgchk, len);
7192                 outCount = fwrite(msg, 1, dbgchk, stdout);
7193                 free(msg);
7194             }
7195         }
7196     }
7197     else
7198       outCount = write(cp->fdTo, message, count);
7199
7200     if (outCount == -1)
7201       *outError = errno;
7202     else
7203       *outError = 0;
7204
7205     return outCount;
7206 }
7207
7208 /* Output message to process, with "ms" milliseconds of delay
7209    between each character. This is needed when sending the logon
7210    script to ICC, which for some reason doesn't like the
7211    instantaneous send. */
7212 int
7213 OutputToProcessDelayed (ProcRef pr, char *message, int count, int *outError, long msdelay)
7214 {
7215     ChildProc *cp = (ChildProc *) pr;
7216     int outCount = 0;
7217     int r;
7218
7219     while (count--) {
7220         r = write(cp->fdTo, message++, 1);
7221         if (r == -1) {
7222             *outError = errno;
7223             return outCount;
7224         }
7225         ++outCount;
7226         if (msdelay >= 0)
7227           TimeDelay(msdelay);
7228     }
7229
7230     return outCount;
7231 }
7232
7233 /****   Animation code by Hugh Fisher, DCS, ANU.
7234
7235         Known problem: if a window overlapping the board is
7236         moved away while a piece is being animated underneath,
7237         the newly exposed area won't be updated properly.
7238         I can live with this.
7239
7240         Known problem: if you look carefully at the animation
7241         of pieces in mono mode, they are being drawn as solid
7242         shapes without interior detail while moving. Fixing
7243         this would be a major complication for minimal return.
7244 ****/
7245
7246 /*      Masks for XPM pieces. Black and white pieces can have
7247         different shapes, but in the interest of retaining my
7248         sanity pieces must have the same outline on both light
7249         and dark squares, and all pieces must use the same
7250         background square colors/images.                */
7251
7252 static int xpmDone = 0;
7253
7254 static void
7255 CreateAnimMasks (int pieceDepth)
7256 {
7257   ChessSquare   piece;
7258   Pixmap        buf;
7259   GC            bufGC, maskGC;
7260   int           kind, n;
7261   unsigned long plane;
7262   XGCValues     values;
7263
7264   /* Need a bitmap just to get a GC with right depth */
7265   buf = XCreatePixmap(xDisplay, xBoardWindow,
7266                         8, 8, 1);
7267   values.foreground = 1;
7268   values.background = 0;
7269   /* Don't use XtGetGC, not read only */
7270   maskGC = XCreateGC(xDisplay, buf,
7271                     GCForeground | GCBackground, &values);
7272   XFreePixmap(xDisplay, buf);
7273
7274   buf = XCreatePixmap(xDisplay, xBoardWindow,
7275                       squareSize, squareSize, pieceDepth);
7276   values.foreground = XBlackPixel(xDisplay, xScreen);
7277   values.background = XWhitePixel(xDisplay, xScreen);
7278   bufGC = XCreateGC(xDisplay, buf,
7279                     GCForeground | GCBackground, &values);
7280
7281   for (piece = WhitePawn; piece <= BlackKing; piece++) {
7282     /* Begin with empty mask */
7283     if(!xpmDone) // [HGM] pieces: keep using existing
7284     xpmMask[piece] = XCreatePixmap(xDisplay, xBoardWindow,
7285                                  squareSize, squareSize, 1);
7286     XSetFunction(xDisplay, maskGC, GXclear);
7287     XFillRectangle(xDisplay, xpmMask[piece], maskGC,
7288                    0, 0, squareSize, squareSize);
7289
7290     /* Take a copy of the piece */
7291     if (White(piece))
7292       kind = 0;
7293     else
7294       kind = 2;
7295     XSetFunction(xDisplay, bufGC, GXcopy);
7296     XCopyArea(xDisplay, xpmPieceBitmap[kind][((int)piece) % (int)BlackPawn],
7297               buf, bufGC,
7298               0, 0, squareSize, squareSize, 0, 0);
7299
7300     /* XOR the background (light) over the piece */
7301     XSetFunction(xDisplay, bufGC, GXxor);
7302     if (useImageSqs)
7303       XCopyArea(xDisplay, xpmLightSquare, buf, bufGC,
7304                 0, 0, squareSize, squareSize, 0, 0);
7305     else {
7306       XSetForeground(xDisplay, bufGC, lightSquareColor);
7307       XFillRectangle(xDisplay, buf, bufGC, 0, 0, squareSize, squareSize);
7308     }
7309
7310     /* We now have an inverted piece image with the background
7311        erased. Construct mask by just selecting all the non-zero
7312        pixels - no need to reconstruct the original image.      */
7313     XSetFunction(xDisplay, maskGC, GXor);
7314     plane = 1;
7315     /* Might be quicker to download an XImage and create bitmap
7316        data from it rather than this N copies per piece, but it
7317        only takes a fraction of a second and there is a much
7318        longer delay for loading the pieces.             */
7319     for (n = 0; n < pieceDepth; n ++) {
7320       XCopyPlane(xDisplay, buf, xpmMask[piece], maskGC,
7321                  0, 0, squareSize, squareSize,
7322                  0, 0, plane);
7323       plane = plane << 1;
7324     }
7325   }
7326   /* Clean up */
7327   XFreePixmap(xDisplay, buf);
7328   XFreeGC(xDisplay, bufGC);
7329   XFreeGC(xDisplay, maskGC);
7330 }
7331
7332 static void
7333 InitAnimState (AnimState *anim, XWindowAttributes *info)
7334 {
7335   XtGCMask  mask;
7336   XGCValues values;
7337
7338   /* Each buffer is square size, same depth as window */
7339   anim->saveBuf = XCreatePixmap(xDisplay, xBoardWindow,
7340                         squareSize, squareSize, info->depth);
7341   anim->newBuf = XCreatePixmap(xDisplay, xBoardWindow,
7342                         squareSize, squareSize, info->depth);
7343
7344   /* Create a plain GC for blitting */
7345   mask = GCForeground | GCBackground | GCFunction |
7346          GCPlaneMask | GCGraphicsExposures;
7347   values.foreground = XBlackPixel(xDisplay, xScreen);
7348   values.background = XWhitePixel(xDisplay, xScreen);
7349   values.function   = GXcopy;
7350   values.plane_mask = AllPlanes;
7351   values.graphics_exposures = False;
7352   anim->blitGC = XCreateGC(xDisplay, xBoardWindow, mask, &values);
7353
7354   /* Piece will be copied from an existing context at
7355      the start of each new animation/drag. */
7356   anim->pieceGC = XCreateGC(xDisplay, xBoardWindow, 0, &values);
7357
7358   /* Outline will be a read-only copy of an existing */
7359   anim->outlineGC = None;
7360 }
7361
7362 static void
7363 CreateAnimVars ()
7364 {
7365   XWindowAttributes info;
7366
7367   if (xpmDone && gameInfo.variant == oldVariant) return;
7368   if(xpmDone) oldVariant = gameInfo.variant; // first time pieces might not be created yet
7369   XGetWindowAttributes(xDisplay, xBoardWindow, &info);
7370
7371   InitAnimState(&game, &info);
7372   InitAnimState(&player, &info);
7373
7374   /* For XPM pieces, we need bitmaps to use as masks. */
7375   if (useImages)
7376     CreateAnimMasks(info.depth), xpmDone = 1;
7377 }
7378
7379 #ifndef HAVE_USLEEP
7380
7381 static Boolean frameWaiting;
7382
7383 static RETSIGTYPE
7384 FrameAlarm (int sig)
7385 {
7386   frameWaiting = False;
7387   /* In case System-V style signals.  Needed?? */
7388   signal(SIGALRM, FrameAlarm);
7389 }
7390
7391 static void
7392 FrameDelay (int time)
7393 {
7394   struct itimerval delay;
7395
7396   XSync(xDisplay, False);
7397
7398   if (time > 0) {
7399     frameWaiting = True;
7400     signal(SIGALRM, FrameAlarm);
7401     delay.it_interval.tv_sec =
7402       delay.it_value.tv_sec = time / 1000;
7403     delay.it_interval.tv_usec =
7404       delay.it_value.tv_usec = (time % 1000) * 1000;
7405     setitimer(ITIMER_REAL, &delay, NULL);
7406     while (frameWaiting) pause();
7407     delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
7408     delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
7409     setitimer(ITIMER_REAL, &delay, NULL);
7410   }
7411 }
7412
7413 #else
7414
7415 static void
7416 FrameDelay (int time)
7417 {
7418   XSync(xDisplay, False);
7419   if (time > 0)
7420     usleep(time * 1000);
7421 }
7422
7423 #endif
7424
7425 void
7426 DoSleep (int n)
7427 {
7428     FrameDelay(n);
7429 }
7430
7431 /*      Convert board position to corner of screen rect and color       */
7432
7433 static void
7434 ScreenSquare (int column, int row, XPoint *pt, int *color)
7435 {
7436   if (flipView) {
7437     pt->x = lineGap + ((BOARD_WIDTH-1)-column) * (squareSize + lineGap);
7438     pt->y = lineGap + row * (squareSize + lineGap);
7439   } else {
7440     pt->x = lineGap + column * (squareSize + lineGap);
7441     pt->y = lineGap + ((BOARD_HEIGHT-1)-row) * (squareSize + lineGap);
7442   }
7443   *color = SquareColor(row, column);
7444 }
7445
7446 /*      Convert window coords to square                 */
7447
7448 static void
7449 BoardSquare (int x, int y, int *column, int *row)
7450 {
7451   *column = EventToSquare(x, BOARD_WIDTH);
7452   if (flipView && *column >= 0)
7453     *column = BOARD_WIDTH - 1 - *column;
7454   *row = EventToSquare(y, BOARD_HEIGHT);
7455   if (!flipView && *row >= 0)
7456     *row = BOARD_HEIGHT - 1 - *row;
7457 }
7458
7459 /*   Utilities  */
7460
7461 #undef Max  /* just in case */
7462 #undef Min
7463 #define Max(a, b) ((a) > (b) ? (a) : (b))
7464 #define Min(a, b) ((a) < (b) ? (a) : (b))
7465
7466 static void
7467 SetRect (XRectangle *rect, int x, int y, int width, int height)
7468 {
7469   rect->x = x;
7470   rect->y = y;
7471   rect->width  = width;
7472   rect->height = height;
7473 }
7474
7475 /*      Test if two frames overlap. If they do, return
7476         intersection rect within old and location of
7477         that rect within new. */
7478
7479 static Boolean
7480 Intersect ( XPoint *old, XPoint *new, int size, XRectangle *area, XPoint *pt)
7481 {
7482   if (old->x > new->x + size || new->x > old->x + size ||
7483       old->y > new->y + size || new->y > old->y + size) {
7484     return False;
7485   } else {
7486     SetRect(area, Max(new->x - old->x, 0), Max(new->y - old->y, 0),
7487             size - abs(old->x - new->x), size - abs(old->y - new->y));
7488     pt->x = Max(old->x - new->x, 0);
7489     pt->y = Max(old->y - new->y, 0);
7490     return True;
7491   }
7492 }
7493
7494 /*      For two overlapping frames, return the rect(s)
7495         in the old that do not intersect with the new.   */
7496
7497 static void
7498 CalcUpdateRects (XPoint *old, XPoint *new, int size, XRectangle update[], int *nUpdates)
7499 {
7500   int        count;
7501
7502   /* If old = new (shouldn't happen) then nothing to draw */
7503   if (old->x == new->x && old->y == new->y) {
7504     *nUpdates = 0;
7505     return;
7506   }
7507   /* Work out what bits overlap. Since we know the rects
7508      are the same size we don't need a full intersect calc. */
7509   count = 0;
7510   /* Top or bottom edge? */
7511   if (new->y > old->y) {
7512     SetRect(&(update[count]), old->x, old->y, size, new->y - old->y);
7513     count ++;
7514   } else if (old->y > new->y) {
7515     SetRect(&(update[count]), old->x, old->y + size - (old->y - new->y),
7516                               size, old->y - new->y);
7517     count ++;
7518   }
7519   /* Left or right edge - don't overlap any update calculated above. */
7520   if (new->x > old->x) {
7521     SetRect(&(update[count]), old->x, Max(new->y, old->y),
7522                               new->x - old->x, size - abs(new->y - old->y));
7523     count ++;
7524   } else if (old->x > new->x) {
7525     SetRect(&(update[count]), new->x + size, Max(new->y, old->y),
7526                               old->x - new->x, size - abs(new->y - old->y));
7527     count ++;
7528   }
7529   /* Done */
7530   *nUpdates = count;
7531 }
7532
7533 /*      Generate a series of frame coords from start->mid->finish.
7534         The movement rate doubles until the half way point is
7535         reached, then halves back down to the final destination,
7536         which gives a nice slow in/out effect. The algorithmn
7537         may seem to generate too many intermediates for short
7538         moves, but remember that the purpose is to attract the
7539         viewers attention to the piece about to be moved and
7540         then to where it ends up. Too few frames would be less
7541         noticeable.                                             */
7542
7543 static void
7544 Tween (XPoint *start, XPoint *mid, XPoint *finish, int factor, XPoint frames[], int *nFrames)
7545 {
7546   int fraction, n, count;
7547
7548   count = 0;
7549
7550   /* Slow in, stepping 1/16th, then 1/8th, ... */
7551   fraction = 1;
7552   for (n = 0; n < factor; n++)
7553     fraction *= 2;
7554   for (n = 0; n < factor; n++) {
7555     frames[count].x = start->x + (mid->x - start->x) / fraction;
7556     frames[count].y = start->y + (mid->y - start->y) / fraction;
7557     count ++;
7558     fraction = fraction / 2;
7559   }
7560
7561   /* Midpoint */
7562   frames[count] = *mid;
7563   count ++;
7564
7565   /* Slow out, stepping 1/2, then 1/4, ... */
7566   fraction = 2;
7567   for (n = 0; n < factor; n++) {
7568     frames[count].x = finish->x - (finish->x - mid->x) / fraction;
7569     frames[count].y = finish->y - (finish->y - mid->y) / fraction;
7570     count ++;
7571     fraction = fraction * 2;
7572   }
7573   *nFrames = count;
7574 }
7575
7576 /*      Draw a piece on the screen without disturbing what's there      */
7577
7578 static void
7579 SelectGCMask (ChessSquare piece, GC *clip, GC *outline, Pixmap *mask)
7580 {
7581   GC source;
7582
7583   /* Bitmap for piece being moved. */
7584   if (appData.monoMode) {
7585       *mask = *pieceToSolid(piece);
7586   } else if (useImages) {
7587 #if HAVE_LIBXPM
7588       *mask = xpmMask[piece];
7589 #else
7590       *mask = ximMaskPm[piece];
7591 #endif
7592   } else {
7593       *mask = *pieceToSolid(piece);
7594   }
7595
7596   /* GC for piece being moved. Square color doesn't matter, but
7597      since it gets modified we make a copy of the original. */
7598   if (White(piece)) {
7599     if (appData.monoMode)
7600       source = bwPieceGC;
7601     else
7602       source = wlPieceGC;
7603   } else {
7604     if (appData.monoMode)
7605       source = wbPieceGC;
7606     else
7607       source = blPieceGC;
7608   }
7609   XCopyGC(xDisplay, source, 0xFFFFFFFF, *clip);
7610
7611   /* Outline only used in mono mode and is not modified */
7612   if (White(piece))
7613     *outline = bwPieceGC;
7614   else
7615     *outline = wbPieceGC;
7616 }
7617
7618 static void
7619 OverlayPiece (ChessSquare piece, GC clip, GC outline,  Drawable dest)
7620 {
7621   int   kind;
7622
7623   if (!useImages) {
7624     /* Draw solid rectangle which will be clipped to shape of piece */
7625     XFillRectangle(xDisplay, dest, clip,
7626                    0, 0, squareSize, squareSize);
7627     if (appData.monoMode)
7628       /* Also draw outline in contrasting color for black
7629          on black / white on white cases                */
7630       XCopyPlane(xDisplay, *pieceToOutline(piece), dest, outline,
7631                  0, 0, squareSize, squareSize, 0, 0, 1);
7632   } else {
7633     /* Copy the piece */
7634     if (White(piece))
7635       kind = 0;
7636     else
7637       kind = 2;
7638     if(appData.upsideDown && flipView) kind ^= 2;
7639     XCopyArea(xDisplay, xpmPieceBitmap[kind][piece],
7640               dest, clip,
7641               0, 0, squareSize, squareSize,
7642               0, 0);
7643   }
7644 }
7645
7646 /* Animate the movement of a single piece */
7647
7648 static void
7649 BeginAnimation (AnimState *anim, ChessSquare piece, int startColor, XPoint *start)
7650 {
7651   Pixmap mask;
7652
7653   if(appData.upsideDown && flipView) piece += piece < BlackPawn ? BlackPawn : -BlackPawn;
7654   /* The old buffer is initialised with the start square (empty) */
7655   BlankSquare(start->x, start->y, startColor, EmptySquare, anim->saveBuf, 0);
7656   anim->prevFrame = *start;
7657
7658   /* The piece will be drawn using its own bitmap as a matte    */
7659   SelectGCMask(piece, &anim->pieceGC, &anim->outlineGC, &mask);
7660   XSetClipMask(xDisplay, anim->pieceGC, mask);
7661 }
7662
7663 static void
7664 AnimationFrame (AnimState *anim, XPoint *frame, ChessSquare piece)
7665 {
7666   XRectangle updates[4];
7667   XRectangle overlap;
7668   XPoint     pt;
7669   int        count, i;
7670
7671   /* Save what we are about to draw into the new buffer */
7672   XCopyArea(xDisplay, xBoardWindow, anim->newBuf, anim->blitGC,
7673             frame->x, frame->y, squareSize, squareSize,
7674             0, 0);
7675
7676   /* Erase bits of the previous frame */
7677   if (Intersect(&anim->prevFrame, frame, squareSize, &overlap, &pt)) {
7678     /* Where the new frame overlapped the previous,
7679        the contents in newBuf are wrong. */
7680     XCopyArea(xDisplay, anim->saveBuf, anim->newBuf, anim->blitGC,
7681               overlap.x, overlap.y,
7682               overlap.width, overlap.height,
7683               pt.x, pt.y);
7684     /* Repaint the areas in the old that don't overlap new */
7685     CalcUpdateRects(&anim->prevFrame, frame, squareSize, updates, &count);
7686     for (i = 0; i < count; i++)
7687       XCopyArea(xDisplay, anim->saveBuf, xBoardWindow, anim->blitGC,
7688                 updates[i].x - anim->prevFrame.x,
7689                 updates[i].y - anim->prevFrame.y,
7690                 updates[i].width, updates[i].height,
7691                 updates[i].x, updates[i].y);
7692   } else {
7693     /* Easy when no overlap */
7694     XCopyArea(xDisplay, anim->saveBuf, xBoardWindow, anim->blitGC,
7695                   0, 0, squareSize, squareSize,
7696                   anim->prevFrame.x, anim->prevFrame.y);
7697   }
7698
7699   /* Save this frame for next time round */
7700   XCopyArea(xDisplay, anim->newBuf, anim->saveBuf, anim->blitGC,
7701                 0, 0, squareSize, squareSize,
7702                 0, 0);
7703   anim->prevFrame = *frame;
7704
7705   /* Draw piece over original screen contents, not current,
7706      and copy entire rect. Wipes out overlapping piece images. */
7707   OverlayPiece(piece, anim->pieceGC, anim->outlineGC, anim->newBuf);
7708   XCopyArea(xDisplay, anim->newBuf, xBoardWindow, anim->blitGC,
7709                 0, 0, squareSize, squareSize,
7710                 frame->x, frame->y);
7711 }
7712
7713 static void
7714 EndAnimation (AnimState *anim, XPoint *finish)
7715 {
7716   XRectangle updates[4];
7717   XRectangle overlap;
7718   XPoint     pt;
7719   int        count, i;
7720
7721   /* The main code will redraw the final square, so we
7722      only need to erase the bits that don't overlap.    */
7723   if (Intersect(&anim->prevFrame, finish, squareSize, &overlap, &pt)) {
7724     CalcUpdateRects(&anim->prevFrame, finish, squareSize, updates, &count);
7725     for (i = 0; i < count; i++)
7726       XCopyArea(xDisplay, anim->saveBuf, xBoardWindow, anim->blitGC,
7727                 updates[i].x - anim->prevFrame.x,
7728                 updates[i].y - anim->prevFrame.y,
7729                 updates[i].width, updates[i].height,
7730                 updates[i].x, updates[i].y);
7731   } else {
7732     XCopyArea(xDisplay, anim->saveBuf, xBoardWindow, anim->blitGC,
7733                 0, 0, squareSize, squareSize,
7734                 anim->prevFrame.x, anim->prevFrame.y);
7735   }
7736 }
7737
7738 static void
7739 FrameSequence (AnimState *anim, ChessSquare piece, int startColor, XPoint *start, XPoint *finish, XPoint frames[], int nFrames)
7740 {
7741   int n;
7742
7743   BeginAnimation(anim, piece, startColor, start);
7744   for (n = 0; n < nFrames; n++) {
7745     AnimationFrame(anim, &(frames[n]), piece);
7746     FrameDelay(appData.animSpeed);
7747   }
7748   EndAnimation(anim, finish);
7749 }
7750
7751 void
7752 AnimateAtomicCapture (Board board, int fromX, int fromY, int toX, int toY)
7753 {
7754     int i, x, y;
7755     ChessSquare piece = board[fromY][toY];
7756     board[fromY][toY] = EmptySquare;
7757     DrawPosition(FALSE, board);
7758     if (flipView) {
7759         x = lineGap + ((BOARD_WIDTH-1)-toX) * (squareSize + lineGap);
7760         y = lineGap + toY * (squareSize + lineGap);
7761     } else {
7762         x = lineGap + toX * (squareSize + lineGap);
7763         y = lineGap + ((BOARD_HEIGHT-1)-toY) * (squareSize + lineGap);
7764     }
7765     for(i=1; i<4*kFactor; i++) {
7766         int r = squareSize * 9 * i/(20*kFactor - 5);
7767         XFillArc(xDisplay, xBoardWindow, highlineGC,
7768                 x + squareSize/2 - r, y+squareSize/2 - r, 2*r, 2*r, 0, 64*360);
7769         FrameDelay(appData.animSpeed);
7770     }
7771     board[fromY][toY] = piece;
7772 }
7773
7774 /* Main control logic for deciding what to animate and how */
7775
7776 void
7777 AnimateMove (Board board, int fromX, int fromY, int toX, int toY)
7778 {
7779   ChessSquare piece;
7780   int hop;
7781   XPoint      start, finish, mid;
7782   XPoint      frames[kFactor * 2 + 1];
7783   int         nFrames, startColor, endColor;
7784
7785   /* Are we animating? */
7786   if (!appData.animate || appData.blindfold)
7787     return;
7788
7789   if(board[toY][toX] == WhiteRook && board[fromY][fromX] == WhiteKing ||
7790      board[toY][toX] == BlackRook && board[fromY][fromX] == BlackKing)
7791         return; // [HGM] FRC: no animtion of FRC castlings, as to-square is not true to-square
7792
7793   if (fromY < 0 || fromX < 0 || toX < 0 || toY < 0) return;
7794   piece = board[fromY][fromX];
7795   if (piece >= EmptySquare) return;
7796
7797 #if DONT_HOP
7798   hop = FALSE;
7799 #else
7800   hop = abs(fromX-toX) == 1 && abs(fromY-toY) == 2 || abs(fromX-toX) == 2 && abs(fromY-toY) == 1;
7801 #endif
7802
7803   ScreenSquare(fromX, fromY, &start, &startColor);
7804   ScreenSquare(toX, toY, &finish, &endColor);
7805
7806   if (hop) {
7807     /* Knight: make straight movement then diagonal */
7808     if (abs(toY - fromY) < abs(toX - fromX)) {
7809        mid.x = start.x + (finish.x - start.x) / 2;
7810        mid.y = start.y;
7811      } else {
7812        mid.x = start.x;
7813        mid.y = start.y + (finish.y - start.y) / 2;
7814      }
7815   } else {
7816     mid.x = start.x + (finish.x - start.x) / 2;
7817     mid.y = start.y + (finish.y - start.y) / 2;
7818   }
7819
7820   /* Don't use as many frames for very short moves */
7821   if (abs(toY - fromY) + abs(toX - fromX) <= 2)
7822     Tween(&start, &mid, &finish, kFactor - 1, frames, &nFrames);
7823   else
7824     Tween(&start, &mid, &finish, kFactor, frames, &nFrames);
7825   FrameSequence(&game, piece, startColor, &start, &finish, frames, nFrames);
7826   if(Explode(board, fromX, fromY, toX, toY)) { // mark as damaged
7827     int i,j;
7828     for(i=0; i<BOARD_WIDTH; i++) for(j=0; j<BOARD_HEIGHT; j++)
7829       if((i-toX)*(i-toX) + (j-toY)*(j-toY) < 6) damage[0][j][i] = True;
7830   }
7831
7832   /* Be sure end square is redrawn */
7833   damage[0][toY][toX] = True;
7834 }
7835
7836 void
7837 DragPieceBegin (int x, int y, Boolean instantly)
7838 {
7839     int  boardX, boardY, color;
7840     XPoint corner;
7841
7842     /* Are we animating? */
7843     if (!appData.animateDragging || appData.blindfold)
7844       return;
7845
7846     /* Figure out which square we start in and the
7847        mouse position relative to top left corner. */
7848     BoardSquare(x, y, &boardX, &boardY);
7849     player.startBoardX = boardX;
7850     player.startBoardY = boardY;
7851     ScreenSquare(boardX, boardY, &corner, &color);
7852     player.startSquare  = corner;
7853     player.startColor   = color;
7854     /* As soon as we start dragging, the piece will jump slightly to
7855        be centered over the mouse pointer. */
7856     player.mouseDelta.x = squareSize/2;
7857     player.mouseDelta.y = squareSize/2;
7858     /* Initialise animation */
7859     player.dragPiece = PieceForSquare(boardX, boardY);
7860     /* Sanity check */
7861     if (player.dragPiece >= 0 && player.dragPiece < EmptySquare) {
7862         player.dragActive = True;
7863         BeginAnimation(&player, player.dragPiece, color, &corner);
7864         /* Mark this square as needing to be redrawn. Note that
7865            we don't remove the piece though, since logically (ie
7866            as seen by opponent) the move hasn't been made yet. */
7867            if(boardX == BOARD_RGHT+1 && PieceForSquare(boardX-1, boardY) > 1 ||
7868               boardX == BOARD_LEFT-2 && PieceForSquare(boardX+1, boardY) > 1)
7869            XCopyArea(xDisplay, xBoardWindow, player.saveBuf, player.blitGC,
7870                      corner.x, corner.y, squareSize, squareSize,
7871                      0, 0); // [HGM] zh: unstack in stead of grab
7872            if(gatingPiece != EmptySquare) {
7873                /* Kludge alert: When gating we want the introduced
7874                   piece to appear on the from square. To generate an
7875                   image of it, we draw it on the board, copy the image,
7876                   and draw the original piece again. */
7877                ChessSquare piece = boards[currentMove][boardY][boardX];
7878                DrawSquare(boardY, boardX, gatingPiece, 0);
7879                XCopyArea(xDisplay, xBoardWindow, player.saveBuf, player.blitGC,
7880                      corner.x, corner.y, squareSize, squareSize, 0, 0);
7881                DrawSquare(boardY, boardX, piece, 0);
7882            }
7883         damage[0][boardY][boardX] = True;
7884     } else {
7885         player.dragActive = False;
7886     }
7887 }
7888
7889 void
7890 ChangeDragPiece (ChessSquare piece)
7891 {
7892   Pixmap mask;
7893   player.dragPiece = piece;
7894   /* The piece will be drawn using its own bitmap as a matte    */
7895   SelectGCMask(piece, &player.pieceGC, &player.outlineGC, &mask);
7896   XSetClipMask(xDisplay, player.pieceGC, mask);
7897 }
7898
7899 static void
7900 DragPieceMove (int x, int y)
7901 {
7902     XPoint corner;
7903
7904     /* Are we animating? */
7905     if (!appData.animateDragging || appData.blindfold)
7906       return;
7907
7908     /* Sanity check */
7909     if (! player.dragActive)
7910       return;
7911     /* Move piece, maintaining same relative position
7912        of mouse within square    */
7913     corner.x = x - player.mouseDelta.x;
7914     corner.y = y - player.mouseDelta.y;
7915     AnimationFrame(&player, &corner, player.dragPiece);
7916 #if HIGHDRAG*0
7917     if (appData.highlightDragging) {
7918         int boardX, boardY;
7919         BoardSquare(x, y, &boardX, &boardY);
7920         SetHighlights(fromX, fromY, boardX, boardY);
7921     }
7922 #endif
7923 }
7924
7925 void
7926 DragPieceEnd (int x, int y)
7927 {
7928     int boardX, boardY, color;
7929     XPoint corner;
7930
7931     /* Are we animating? */
7932     if (!appData.animateDragging || appData.blindfold)
7933       return;
7934
7935     /* Sanity check */
7936     if (! player.dragActive)
7937       return;
7938     /* Last frame in sequence is square piece is
7939        placed on, which may not match mouse exactly. */
7940     BoardSquare(x, y, &boardX, &boardY);
7941     ScreenSquare(boardX, boardY, &corner, &color);
7942     EndAnimation(&player, &corner);
7943
7944     /* Be sure end square is redrawn */
7945     damage[0][boardY][boardX] = True;
7946
7947     /* This prevents weird things happening with fast successive
7948        clicks which on my Sun at least can cause motion events
7949        without corresponding press/release. */
7950     player.dragActive = False;
7951 }
7952
7953 /* Handle expose event while piece being dragged */
7954
7955 static void
7956 DrawDragPiece ()
7957 {
7958   if (!player.dragActive || appData.blindfold)
7959     return;
7960
7961   /* What we're doing: logically, the move hasn't been made yet,
7962      so the piece is still in it's original square. But visually
7963      it's being dragged around the board. So we erase the square
7964      that the piece is on and draw it at the last known drag point. */
7965   BlankSquare(player.startSquare.x, player.startSquare.y,
7966                 player.startColor, EmptySquare, xBoardWindow, 1);
7967   AnimationFrame(&player, &player.prevFrame, player.dragPiece);
7968   damage[0][player.startBoardY][player.startBoardX] = TRUE;
7969 }
7970
7971 #include <sys/ioctl.h>
7972 int
7973 get_term_width ()
7974 {
7975     int fd, default_width;
7976
7977     fd = STDIN_FILENO;
7978     default_width = 79; // this is FICS default anyway...
7979
7980 #if !defined(TIOCGWINSZ) && defined(TIOCGSIZE)
7981     struct ttysize win;
7982     if (!ioctl(fd, TIOCGSIZE, &win))
7983         default_width = win.ts_cols;
7984 #elif defined(TIOCGWINSZ)
7985     struct winsize win;
7986     if (!ioctl(fd, TIOCGWINSZ, &win))
7987         default_width = win.ws_col;
7988 #endif
7989     return default_width;
7990 }
7991
7992 void
7993 update_ics_width ()
7994 {
7995   static int old_width = 0;
7996   int new_width = get_term_width();
7997
7998   if (old_width != new_width)
7999     ics_printf("set width %d\n", new_width);
8000   old_width = new_width;
8001 }
8002
8003 void
8004 NotifyFrontendLogin ()
8005 {
8006     update_ics_width();
8007 }
8008
8009 /* [AS] Arrow highlighting support */
8010
8011 static double A_WIDTH = 5; /* Width of arrow body */
8012
8013 #define A_HEIGHT_FACTOR 6   /* Length of arrow "point", relative to body width */
8014 #define A_WIDTH_FACTOR  3   /* Width of arrow "point", relative to body width */
8015
8016 static double
8017 Sqr (double x)
8018 {
8019     return x*x;
8020 }
8021
8022 static int
8023 Round (double x)
8024 {
8025     return (int) (x + 0.5);
8026 }
8027
8028 void
8029 SquareToPos (int rank, int file, int *x, int *y)
8030 {
8031     if (flipView) {
8032         *x = lineGap + ((BOARD_WIDTH-1)-file) * (squareSize + lineGap);
8033         *y = lineGap + rank * (squareSize + lineGap);
8034     } else {
8035         *x = lineGap + file * (squareSize + lineGap);
8036         *y = lineGap + ((BOARD_HEIGHT-1)-rank) * (squareSize + lineGap);
8037     }
8038 }
8039
8040 /* Draw an arrow between two points using current settings */
8041 void
8042 DrawArrowBetweenPoints (int s_x, int s_y, int d_x, int d_y)
8043 {
8044     XPoint arrow[8];
8045     double dx, dy, j, k, x, y;
8046
8047     if( d_x == s_x ) {
8048         int h = (d_y > s_y) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
8049
8050         arrow[0].x = s_x + A_WIDTH + 0.5;
8051         arrow[0].y = s_y;
8052
8053         arrow[1].x = s_x + A_WIDTH + 0.5;
8054         arrow[1].y = d_y - h;
8055
8056         arrow[2].x = arrow[1].x + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
8057         arrow[2].y = d_y - h;
8058
8059         arrow[3].x = d_x;
8060         arrow[3].y = d_y;
8061
8062         arrow[5].x = arrow[1].x - 2*A_WIDTH + 0.5;
8063         arrow[5].y = d_y - h;
8064
8065         arrow[4].x = arrow[5].x - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
8066         arrow[4].y = d_y - h;
8067
8068         arrow[6].x = arrow[1].x - 2*A_WIDTH + 0.5;
8069         arrow[6].y = s_y;
8070     }
8071     else if( d_y == s_y ) {
8072         int w = (d_x > s_x) ? +A_WIDTH*A_HEIGHT_FACTOR : -A_WIDTH*A_HEIGHT_FACTOR;
8073
8074         arrow[0].x = s_x;
8075         arrow[0].y = s_y + A_WIDTH + 0.5;
8076
8077         arrow[1].x = d_x - w;
8078         arrow[1].y = s_y + A_WIDTH + 0.5;
8079
8080         arrow[2].x = d_x - w;
8081         arrow[2].y = arrow[1].y + A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
8082
8083         arrow[3].x = d_x;
8084         arrow[3].y = d_y;
8085
8086         arrow[5].x = d_x - w;
8087         arrow[5].y = arrow[1].y - 2*A_WIDTH + 0.5;
8088
8089         arrow[4].x = d_x - w;
8090         arrow[4].y = arrow[5].y - A_WIDTH*(A_WIDTH_FACTOR-1) + 0.5;
8091
8092         arrow[6].x = s_x;
8093         arrow[6].y = arrow[1].y - 2*A_WIDTH + 0.5;
8094     }
8095     else {
8096         /* [AS] Needed a lot of paper for this! :-) */
8097         dy = (double) (d_y - s_y) / (double) (d_x - s_x);
8098         dx = (double) (s_x - d_x) / (double) (s_y - d_y);
8099
8100         j = sqrt( Sqr(A_WIDTH) / (1.0 + Sqr(dx)) );
8101
8102         k = sqrt( Sqr(A_WIDTH*A_HEIGHT_FACTOR) / (1.0 + Sqr(dy)) );
8103
8104         x = s_x;
8105         y = s_y;
8106
8107         arrow[0].x = Round(x - j);
8108         arrow[0].y = Round(y + j*dx);
8109
8110         arrow[1].x = Round(arrow[0].x + 2*j);   // [HGM] prevent width to be affected by rounding twice
8111         arrow[1].y = Round(arrow[0].y - 2*j*dx);
8112
8113         if( d_x > s_x ) {
8114             x = (double) d_x - k;
8115             y = (double) d_y - k*dy;
8116         }
8117         else {
8118             x = (double) d_x + k;
8119             y = (double) d_y + k*dy;
8120         }
8121
8122         x = Round(x); y = Round(y); // [HGM] make sure width of shaft is rounded the same way on both ends
8123
8124         arrow[6].x = Round(x - j);
8125         arrow[6].y = Round(y + j*dx);
8126
8127         arrow[2].x = Round(arrow[6].x + 2*j);
8128         arrow[2].y = Round(arrow[6].y - 2*j*dx);
8129
8130         arrow[3].x = Round(arrow[2].x + j*(A_WIDTH_FACTOR-1));
8131         arrow[3].y = Round(arrow[2].y - j*(A_WIDTH_FACTOR-1)*dx);
8132
8133         arrow[4].x = d_x;
8134         arrow[4].y = d_y;
8135
8136         arrow[5].x = Round(arrow[6].x - j*(A_WIDTH_FACTOR-1));
8137         arrow[5].y = Round(arrow[6].y + j*(A_WIDTH_FACTOR-1)*dx);
8138     }
8139
8140     XFillPolygon(xDisplay, xBoardWindow, highlineGC, arrow, 7, Nonconvex, CoordModeOrigin);
8141     if(appData.monoMode) arrow[7] = arrow[0], XDrawLines(xDisplay, xBoardWindow, darkSquareGC, arrow, 8, CoordModeOrigin);
8142 //    Polygon( hdc, arrow, 7 );
8143 }
8144
8145 void
8146 ArrowDamage (int s_col, int s_row, int d_col, int d_row)
8147 {
8148     int hor, vert, i;
8149     hor = 64*s_col + 32; vert = 64*s_row + 32;
8150     for(i=0; i<= 64; i++) {
8151             damage[0][vert+6>>6][hor+6>>6] = True;
8152             damage[0][vert-6>>6][hor+6>>6] = True;
8153             damage[0][vert+6>>6][hor-6>>6] = True;
8154             damage[0][vert-6>>6][hor-6>>6] = True;
8155             hor += d_col - s_col; vert += d_row - s_row;
8156     }
8157 }
8158
8159 /* [AS] Draw an arrow between two squares */
8160 void
8161 DrawArrowBetweenSquares (int s_col, int s_row, int d_col, int d_row)
8162 {
8163     int s_x, s_y, d_x, d_y;
8164
8165     if( s_col == d_col && s_row == d_row ) {
8166         return;
8167     }
8168
8169     /* Get source and destination points */
8170     SquareToPos( s_row, s_col, &s_x, &s_y);
8171     SquareToPos( d_row, d_col, &d_x, &d_y);
8172
8173     if( d_y > s_y ) {
8174         d_y += squareSize / 2 - squareSize / 4; // [HGM] round towards same centers on all sides!
8175     }
8176     else if( d_y < s_y ) {
8177         d_y += squareSize / 2 + squareSize / 4;
8178     }
8179     else {
8180         d_y += squareSize / 2;
8181     }
8182
8183     if( d_x > s_x ) {
8184         d_x += squareSize / 2 - squareSize / 4;
8185     }
8186     else if( d_x < s_x ) {
8187         d_x += squareSize / 2 + squareSize / 4;
8188     }
8189     else {
8190         d_x += squareSize / 2;
8191     }
8192
8193     s_x += squareSize / 2;
8194     s_y += squareSize / 2;
8195
8196     /* Adjust width */
8197     A_WIDTH = squareSize / 14.; //[HGM] make float
8198
8199     DrawArrowBetweenPoints( s_x, s_y, d_x, d_y );
8200     ArrowDamage(s_col, s_row, d_col, d_row);
8201 }
8202
8203 Boolean
8204 IsDrawArrowEnabled ()
8205 {
8206     return appData.highlightMoveWithArrow && squareSize >= 32;
8207 }
8208
8209 void
8210 DrawArrowHighlight (int fromX, int fromY, int toX,int toY)
8211 {
8212     if( IsDrawArrowEnabled() && fromX >= 0 && fromY >= 0 && toX >= 0 && toY >= 0)
8213         DrawArrowBetweenSquares(fromX, fromY, toX, toY);
8214 }
8215
8216 void
8217 UpdateLogos (int displ)
8218 {
8219     return; // no logos in XBoard yet
8220 }
8221