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