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