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