Better cleansing of xboard.c from X11 types
[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 #include <cairo/cairo.h>
65 #include <cairo/cairo-xlib.h>
66 #include <gtk/gtk.h>
67
68 #if !OMIT_SOCKETS
69 # if HAVE_SYS_SOCKET_H
70 #  include <sys/socket.h>
71 #  include <netinet/in.h>
72 #  include <netdb.h>
73 # else /* not HAVE_SYS_SOCKET_H */
74 #  if HAVE_LAN_SOCKET_H
75 #   include <lan/socket.h>
76 #   include <lan/in.h>
77 #   include <lan/netdb.h>
78 #  else /* not HAVE_LAN_SOCKET_H */
79 #   define OMIT_SOCKETS 1
80 #  endif /* not HAVE_LAN_SOCKET_H */
81 # endif /* not HAVE_SYS_SOCKET_H */
82 #endif /* !OMIT_SOCKETS */
83
84 #if STDC_HEADERS
85 # include <stdlib.h>
86 # include <string.h>
87 #else /* not STDC_HEADERS */
88 extern char *getenv();
89 # if HAVE_STRING_H
90 #  include <string.h>
91 # else /* not HAVE_STRING_H */
92 #  include <strings.h>
93 # endif /* not HAVE_STRING_H */
94 #endif /* not STDC_HEADERS */
95
96 #if HAVE_SYS_FCNTL_H
97 # include <sys/fcntl.h>
98 #else /* not HAVE_SYS_FCNTL_H */
99 # if HAVE_FCNTL_H
100 #  include <fcntl.h>
101 # endif /* HAVE_FCNTL_H */
102 #endif /* not HAVE_SYS_FCNTL_H */
103
104 #if HAVE_SYS_SYSTEMINFO_H
105 # include <sys/systeminfo.h>
106 #endif /* HAVE_SYS_SYSTEMINFO_H */
107
108 #if TIME_WITH_SYS_TIME
109 # include <sys/time.h>
110 # include <time.h>
111 #else
112 # if HAVE_SYS_TIME_H
113 #  include <sys/time.h>
114 # else
115 #  include <time.h>
116 # endif
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #if HAVE_SYS_WAIT_H
124 # include <sys/wait.h>
125 #endif
126
127 #if HAVE_DIRENT_H
128 # include <dirent.h>
129 # define NAMLEN(dirent) strlen((dirent)->d_name)
130 # define HAVE_DIR_STRUCT
131 #else
132 # define dirent direct
133 # define NAMLEN(dirent) (dirent)->d_namlen
134 # if HAVE_SYS_NDIR_H
135 #  include <sys/ndir.h>
136 #  define HAVE_DIR_STRUCT
137 # endif
138 # if HAVE_SYS_DIR_H
139 #  include <sys/dir.h>
140 #  define HAVE_DIR_STRUCT
141 # endif
142 # if HAVE_NDIR_H
143 #  include <ndir.h>
144 #  define HAVE_DIR_STRUCT
145 # endif
146 #endif
147
148 #if ENABLE_NLS
149 #include <locale.h>
150 #endif
151
152 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
153 #include "common.h"
154
155 #include "frontend.h"
156 #include "backend.h"
157 #include "backendz.h"
158 #include "moves.h"
159 #include "xboard.h"
160 #include "xboard2.h"
161 #include "childio.h"
162 #include "xgamelist.h"
163 #include "xhistory.h"
164 #include "menus.h"
165 #include "board.h"
166 #include "dialogs.h"
167 #include "engineoutput.h"
168 #include "usystem.h"
169 #include "gettext.h"
170 #include "draw.h"
171
172
173 #ifdef __EMX__
174 #ifndef HAVE_USLEEP
175 #define HAVE_USLEEP
176 #endif
177 #define usleep(t)   _sleep2(((t)+500)/1000)
178 #endif
179
180 #ifdef ENABLE_NLS
181 # define  _(s) gettext (s)
182 # define N_(s) gettext_noop (s)
183 #else
184 # define  _(s) (s)
185 # define N_(s)  s
186 #endif
187
188 int main P((int argc, char **argv));
189 RETSIGTYPE CmailSigHandler P((int sig));
190 RETSIGTYPE IntSigHandler P((int sig));
191 RETSIGTYPE TermSizeSigHandler P((int sig));
192 #if ENABLE_NLS
193 char *InsertPxlSize P((char *pattern, int targetPxlSize));
194 XFontSet CreateFontSet P((char *base_fnt_lst));
195 #else
196 char *FindFont P((char *pattern, int targetPxlSize));
197 #endif
198 void DelayedDrag P((void));
199 void ICSInputBoxPopUp P((void));
200 #ifdef TODO_GTK
201 static void MoveTypeInProc P((Widget widget, caddr_t unused, XEvent *event));
202 void HandlePV P((Widget w, XEvent * event,
203                      String * params, Cardinal * nParams));
204 void DrawPositionProc P((Widget w, XEvent *event,
205                      String *prms, Cardinal *nprms));
206 void CommentClick P((Widget w, XEvent * event,
207                    String * params, Cardinal * nParams));
208 void SelectCommand P((Widget w, XtPointer client_data, XtPointer call_data));
209 void KeyBindingProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
210 void QuitWrapper P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
211 static void EnterKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
212 static void UpKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
213 static void DownKeyProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
214 void TempBackwardProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
215 void TempForwardProc P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
216 void ManInner P((Widget w, XEvent *event, String *prms, Cardinal *nprms));
217 void SelectMove P((Widget w, XEvent * event, String * params, Cardinal * nParams));
218 #endif
219 Boolean TempBackwardActive = False;
220 void DisplayMove P((int moveNumber));
221 void ICSInitScript P((void));
222 void update_ics_width P(());
223 int CopyMemoProc P(());
224
225 #ifdef TODO_GTK
226 /*
227 * XBoard depends on Xt R4 or higher
228 */
229 int xtVersion = XtSpecificationRelease;
230
231 int xScreen;
232 Display *xDisplay;
233 Window xBoardWindow;
234 Pixel lowTimeWarningColor, dialogColor, buttonColor; // used in widgets
235 Pixmap iconPixmap, wIconPixmap, bIconPixmap, xMarkPixmap;
236 Widget shellWidget, formWidget, boardWidget, titleWidget, dropMenu, menuBarWidget;
237 #if ENABLE_NLS
238 XFontSet fontSet, clockFontSet;
239 #else
240 Font clockFontID;
241 XFontStruct *clockFontStruct;
242 #endif
243 Font coordFontID, countFontID;
244 XFontStruct *coordFontStruct, *countFontStruct;
245 XtAppContext appContext;
246 #else
247 void *shellWidget, *formWidget, *boardWidget, *titleWidget, *dropMenu, *menuBarWidget;
248 void *appContext;
249 GtkWidget       *mainwindow;
250 #endif
251 Option *optList; // contains all widgets of main window
252 char *layoutName;
253
254 char installDir[] = "."; // [HGM] UCI: needed for UCI; probably needs run-time initializtion
255
256 typedef unsigned int BoardSize;
257 BoardSize boardSize;
258 Boolean chessProgram;
259
260 int  minX, minY; // [HGM] placement: volatile limits on upper-left corner
261 int smallLayout = 0, tinyLayout = 0,
262   marginW, marginH, // [HGM] for run-time resizing
263   fromX = -1, fromY = -1, toX, toY, commentUp = False,
264   errorExitStatus = -1, defaultLineGap;
265 #ifdef TODO_GTK
266 Dimension textHeight;
267 Pixel timerForegroundPixel, timerBackgroundPixel;
268 Pixel buttonForegroundPixel, buttonBackgroundPixel;
269 #endif
270 char *chessDir, *programName, *programVersion;
271 Boolean alwaysOnTop = False;
272 char *icsTextMenuString;
273 char *icsNames;
274 char *firstChessProgramNames;
275 char *secondChessProgramNames;
276
277 WindowPlacement wpMain;
278 WindowPlacement wpConsole;
279 WindowPlacement wpComment;
280 WindowPlacement wpMoveHistory;
281 WindowPlacement wpEvalGraph;
282 WindowPlacement wpEngineOutput;
283 WindowPlacement wpGameList;
284 WindowPlacement wpTags;
285
286 /* This magic number is the number of intermediate frames used
287    in each half of the animation. For short moves it's reduced
288    by 1. The total number of frames will be factor * 2 + 1.  */
289 #define kFactor    4
290
291 SizeDefaults sizeDefaults[] = SIZE_DEFAULTS;
292
293 typedef struct {
294     char piece;
295     char* widget;
296 } DropMenuEnables;
297
298 DropMenuEnables dmEnables[] = {
299     { 'P', "Pawn" },
300     { 'N', "Knight" },
301     { 'B', "Bishop" },
302     { 'R', "Rook" },
303     { 'Q', "Queen" }
304 };
305
306 #ifdef TODO_GTK
307 Arg shellArgs[] = {
308     { XtNwidth, 0 },
309     { XtNheight, 0 },
310     { XtNminWidth, 0 },
311     { XtNminHeight, 0 },
312     { XtNmaxWidth, 0 },
313     { XtNmaxHeight, 0 }
314 };
315
316 XtResource clientResources[] = {
317     { "flashCount", "flashCount", XtRInt, sizeof(int),
318         XtOffset(AppDataPtr, flashCount), XtRImmediate,
319         (XtPointer) FLASH_COUNT  },
320 };
321
322 XrmOptionDescRec shellOptions[] = {
323     { "-flashCount", "flashCount", XrmoptionSepArg, NULL },
324     { "-flash", "flashCount", XrmoptionNoArg, "3" },
325     { "-xflash", "flashCount", XrmoptionNoArg, "0" },
326 };
327
328 XtActionsRec boardActions[] = {
329     { "DrawPosition", DrawPositionProc },
330     { "HandlePV", HandlePV },
331     { "SelectPV", SelectPV },
332     { "StopPV", StopPV },
333     { "MenuItem", KeyBindingProc }, // [HGM] generic handler for key bindings
334     { "QuitProc", QuitWrapper },
335     { "ManProc", ManInner },
336     { "TempBackwardProc", TempBackwardProc },
337     { "TempForwardProc", TempForwardProc },
338     { "CommentClick", (XtActionProc) CommentClick },
339     { "GenericPopDown", (XtActionProc) GenericPopDown },
340     { "ErrorPopDown", (XtActionProc) ErrorPopDown },
341     { "CopyMemoProc", (XtActionProc) CopyMemoProc },
342     { "SelectMove", (XtActionProc) SelectMove },
343     { "LoadSelectedProc", LoadSelectedProc },
344     { "SetFilterProc", SetFilterProc },
345     { "TypeInProc", TypeInProc },
346     { "EnterKeyProc", EnterKeyProc },
347     { "UpKeyProc", UpKeyProc },
348     { "DownKeyProc", DownKeyProc },
349     { "WheelProc", WheelProc },
350     { "TabProc", TabProc },
351 };
352 #endif
353
354 char globalTranslations[] =
355   ":<Key>F9: MenuItem(Actions.Resign) \n \
356    :Ctrl<Key>n: MenuItem(File.NewGame) \n \
357    :Meta<Key>V: MenuItem(File.NewVariant) \n \
358    :Ctrl<Key>o: MenuItem(File.LoadGame) \n \
359    :Meta<Key>Next: MenuItem(LoadNextGameProc) \n \
360    :Meta<Key>Prior: MenuItem(LoadPrevGameProc) \n \
361    :Ctrl<Key>Down: LoadSelectedProc(3) \n \
362    :Ctrl<Key>Up: LoadSelectedProc(-3) \n \
363    :Ctrl<Key>s: MenuItem(File.SaveGame) \n \
364    :Ctrl<Key>c: MenuItem(Edit.CopyGame) \n \
365    :Ctrl<Key>v: MenuItem(Edit.PasteGame) \n \
366    :Ctrl<Key>O: MenuItem(File.LoadPosition) \n \
367    :Shift<Key>Next: MenuItem(LoadNextPositionProc) \n \
368    :Shift<Key>Prior: MenuItem(LoadPrevPositionProc) \n \
369    :Ctrl<Key>S: MenuItem(File.SavePosition) \n \
370    :Ctrl<Key>C: MenuItem(Edit.CopyPosition) \n \
371    :Ctrl<Key>V: MenuItem(Edit.PastePosition) \n \
372    :Ctrl<Key>q: MenuItem(File.Quit) \n \
373    :Ctrl<Key>w: MenuItem(Mode.MachineWhite) \n \
374    :Ctrl<Key>b: MenuItem(Mode.MachineBlack) \n \
375    :Ctrl<Key>t: MenuItem(Mode.TwoMachines) \n \
376    :Ctrl<Key>a: MenuItem(Mode.AnalysisMode) \n \
377    :Ctrl<Key>g: MenuItem(Mode.AnalyzeFile) \n \
378    :Ctrl<Key>e: MenuItem(Mode.EditGame) \n \
379    :Ctrl<Key>E: MenuItem(Mode.EditPosition) \n \
380    :Meta<Key>O: MenuItem(View.EngineOutput) \n \
381    :Meta<Key>E: MenuItem(View.EvaluationGraph) \n \
382    :Meta<Key>G: MenuItem(View.GameList) \n \
383    :Meta<Key>H: MenuItem(View.MoveHistory) \n \
384    :<Key>Pause: MenuItem(Mode.Pause) \n \
385    :<Key>F3: MenuItem(Action.Accept) \n \
386    :<Key>F4: MenuItem(Action.Decline) \n \
387    :<Key>F12: MenuItem(Action.Rematch) \n \
388    :<Key>F5: MenuItem(Action.CallFlag) \n \
389    :<Key>F6: MenuItem(Action.Draw) \n \
390    :<Key>F7: MenuItem(Action.Adjourn) \n \
391    :<Key>F8: MenuItem(Action.Abort) \n \
392    :<Key>F10: MenuItem(Action.StopObserving) \n \
393    :<Key>F11: MenuItem(Action.StopExamining) \n \
394    :Ctrl<Key>d: MenuItem(DebugProc) \n \
395    :Meta Ctrl<Key>F12: MenuItem(DebugProc) \n \
396    :Meta<Key>End: MenuItem(Edit.ForwardtoEnd) \n \
397    :Meta<Key>Right: MenuItem(Edit.Forward) \n \
398    :Meta<Key>Home: MenuItem(Edit.BacktoStart) \n \
399    :Meta<Key>Left: MenuItem(Edit.Backward) \n \
400    :<Key>Left: MenuItem(Edit.Backward) \n \
401    :<Key>Right: MenuItem(Edit.Forward) \n \
402    :<Key>Home: MenuItem(Edit.Revert) \n \
403    :<Key>End: MenuItem(Edit.TruncateGame) \n \
404    :Ctrl<Key>m: MenuItem(Engine.MoveNow) \n \
405    :Ctrl<Key>x: MenuItem(Engine.RetractMove) \n \
406    :Meta<Key>J: MenuItem(Options.Adjudications) \n \
407    :Meta<Key>U: MenuItem(Options.CommonEngine) \n \
408    :Meta<Key>T: MenuItem(Options.TimeControl) \n \
409    :Ctrl<Key>P: MenuItem(PonderNextMove) \n "
410 #ifndef OPTIONSDIALOG
411     "\
412    :Ctrl<Key>Q: MenuItem(AlwaysQueenProc) \n \
413    :Ctrl<Key>F: MenuItem(AutoflagProc) \n \
414    :Ctrl<Key>A: MenuItem(AnimateMovingProc) \n \
415    :Ctrl<Key>L: MenuItem(TestLegalityProc) \n \
416    :Ctrl<Key>H: MenuItem(HideThinkingProc) \n "
417 #endif
418    "\
419    :<Key>F1: MenuItem(Help.ManXBoard) \n \
420    :<Key>F2: MenuItem(View.FlipView) \n \
421    :<KeyDown>Return: TempBackwardProc() \n \
422    :<KeyUp>Return: TempForwardProc() \n";
423
424 char ICSInputTranslations[] =
425     "<Key>Up: UpKeyProc() \n "
426     "<Key>Down: DownKeyProc() \n "
427     "<Key>Return: EnterKeyProc() \n";
428
429 // [HGM] vari: another hideous kludge: call extend-end first so we can be sure select-start works,
430 //             as the widget is destroyed before the up-click can call extend-end
431 char commentTranslations[] = "<Btn3Down>: extend-end() select-start() CommentClick() \n";
432
433 #ifdef TODO_GTK
434 String xboardResources[] = {
435     "*Error*translations: #override\\n <Key>Return: ErrorPopDown()",
436     NULL
437   };
438 #endif
439
440 /* Max possible square size */
441 #define MAXSQSIZE 256
442
443 static int xpm_avail[MAXSQSIZE];
444
445 #ifdef HAVE_DIR_STRUCT
446
447 /* Extract piece size from filename */
448 static int
449 xpm_getsize (char *name, int len, char *ext)
450 {
451     char *p, *d;
452     char buf[10];
453
454     if (len < 4)
455       return 0;
456
457     if ((p=strchr(name, '.')) == NULL ||
458         StrCaseCmp(p+1, ext) != 0)
459       return 0;
460
461     p = name + 3;
462     d = buf;
463
464     while (*p && isdigit(*p))
465       *(d++) = *(p++);
466
467     *d = 0;
468     return atoi(buf);
469 }
470
471 /* Setup xpm_avail */
472 static int
473 xpm_getavail (char *dirname, char *ext)
474 {
475     DIR *dir;
476     struct dirent *ent;
477     int  i;
478
479     for (i=0; i<MAXSQSIZE; ++i)
480       xpm_avail[i] = 0;
481
482     if (appData.debugMode)
483       fprintf(stderr, "XPM dir:%s:ext:%s:\n", dirname, ext);
484
485     dir = opendir(dirname);
486     if (!dir)
487       {
488           fprintf(stderr, _("%s: Can't access XPM directory %s\n"),
489                   programName, dirname);
490           exit(1);
491       }
492
493     while ((ent=readdir(dir)) != NULL) {
494         i = xpm_getsize(ent->d_name, NAMLEN(ent), ext);
495         if (i > 0 && i < MAXSQSIZE)
496           xpm_avail[i] = 1;
497     }
498
499     closedir(dir);
500
501     return 0;
502 }
503
504 void
505 xpm_print_avail (FILE *fp, char *ext)
506 {
507     int i;
508
509     fprintf(fp, _("Available `%s' sizes:\n"), ext);
510     for (i=1; i<MAXSQSIZE; ++i) {
511         if (xpm_avail[i])
512           printf("%d\n", i);
513     }
514 }
515
516 /* Return XPM piecesize closest to size */
517 int
518 xpm_closest_to (char *dirname, int size, char *ext)
519 {
520     int i;
521     int sm_diff = MAXSQSIZE;
522     int sm_index = 0;
523     int diff;
524
525     xpm_getavail(dirname, ext);
526
527     if (appData.debugMode)
528       xpm_print_avail(stderr, ext);
529
530     for (i=1; i<MAXSQSIZE; ++i) {
531         if (xpm_avail[i]) {
532             diff = size - i;
533             diff = (diff<0) ? -diff : diff;
534             if (diff < sm_diff) {
535                 sm_diff = diff;
536                 sm_index = i;
537             }
538         }
539     }
540
541     if (!sm_index) {
542         fprintf(stderr, _("Error: No `%s' files!\n"), ext);
543         exit(1);
544     }
545
546     return sm_index;
547 }
548 #else   /* !HAVE_DIR_STRUCT */
549 /* If we are on a system without a DIR struct, we can't
550    read the directory, so we can't collect a list of
551    filenames, etc., so we can't do any size-fitting. */
552 int
553 xpm_closest_to (char *dirname, int size, char *ext)
554 {
555     fprintf(stderr, _("\
556 Warning: No DIR structure found on this system --\n\
557          Unable to autosize for XPM/XIM pieces.\n\
558    Please report this error to %s.\n\
559    Include system type & operating system in message.\n"), PACKAGE_BUGREPORT););
560     return size;
561 }
562 #endif /* HAVE_DIR_STRUCT */
563
564
565 #ifdef TODO_GTK
566 /* Arrange to catch delete-window events */
567 Atom wm_delete_window;
568 void
569 CatchDeleteWindow (Widget w, String procname)
570 {
571   char buf[MSG_SIZ];
572   XSetWMProtocols(xDisplay, XtWindow(w), &wm_delete_window, 1);
573   snprintf(buf, sizeof(buf), "<Message>WM_PROTOCOLS: %s() \n", procname);
574   XtAugmentTranslations(w, XtParseTranslationTable(buf));
575 }
576 #endif
577
578 void
579 BoardToTop ()
580 {
581   gtk_window_present(GTK_WINDOW(mainwindow));
582 }
583
584 //---------------------------------------------------------------------------------------------------------
585 // some symbol definitions to provide the proper (= XBoard) context for the code in args.h
586 #define XBOARD True
587 #define JAWS_ARGS
588 #define CW_USEDEFAULT (1<<31)
589 #define ICS_TEXT_MENU_SIZE 90
590 #define DEBUG_FILE "xboard.debug"
591 #define SetCurrentDirectory chdir
592 #define GetCurrentDirectory(SIZE, NAME) getcwd(NAME, SIZE)
593 #define OPTCHAR "-"
594 #define SEPCHAR " "
595
596 // The option definition and parsing code common to XBoard and WinBoard is collected in this file
597 #include "args.h"
598
599 // front-end part of option handling
600
601 // [HGM] This platform-dependent table provides the location for storing the color info
602 extern char *crWhite, * crBlack;
603
604 void *
605 colorVariable[] = {
606   &appData.whitePieceColor,
607   &appData.blackPieceColor,
608   &appData.lightSquareColor,
609   &appData.darkSquareColor,
610   &appData.highlightSquareColor,
611   &appData.premoveHighlightColor,
612   &appData.lowTimeWarningColor,
613   NULL,
614   NULL,
615   NULL,
616   NULL,
617   NULL,
618   &crWhite,
619   &crBlack,
620   NULL
621 };
622
623 // [HGM] font: keep a font for each square size, even non-stndard ones
624 #define NUM_SIZES 18
625 #define MAX_SIZE 130
626 Boolean fontIsSet[NUM_FONTS], fontValid[NUM_FONTS][MAX_SIZE];
627 char *fontTable[NUM_FONTS][MAX_SIZE];
628
629 void
630 ParseFont (char *name, int number)
631 { // in XBoard, only 2 of the fonts are currently implemented, and we just copy their name
632   int size;
633   if(sscanf(name, "size%d:", &size)) {
634     // [HGM] font: font is meant for specific boardSize (likely from settings file);
635     //       defer processing it until we know if it matches our board size
636     if(size >= 0 && size<MAX_SIZE) { // for now, fixed limit
637         fontTable[number][size] = strdup(strchr(name, ':')+1);
638         fontValid[number][size] = True;
639     }
640     return;
641   }
642   switch(number) {
643     case 0: // CLOCK_FONT
644         appData.clockFont = strdup(name);
645       break;
646     case 1: // MESSAGE_FONT
647         appData.font = strdup(name);
648       break;
649     case 2: // COORD_FONT
650         appData.coordFont = strdup(name);
651       break;
652     default:
653       return;
654   }
655   fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
656 }
657
658 void
659 SetFontDefaults ()
660 { // only 2 fonts currently
661   appData.clockFont = CLOCK_FONT_NAME;
662   appData.coordFont = COORD_FONT_NAME;
663   appData.font  =   DEFAULT_FONT_NAME;
664 }
665
666 void
667 CreateFonts ()
668 { // no-op, until we identify the code for this already in XBoard and move it here
669 }
670
671 void
672 ParseColor (int n, char *name)
673 { // in XBoard, just copy the color-name string
674   if(colorVariable[n]) *(char**)colorVariable[n] = strdup(name);
675 }
676
677 void
678 ParseTextAttribs (ColorClass cc, char *s)
679 {
680     (&appData.colorShout)[cc] = strdup(s);
681 }
682
683 void
684 ParseBoardSize (void *addr, char *name)
685 {
686     appData.boardSize = strdup(name);
687 }
688
689 void
690 LoadAllSounds ()
691 { // In XBoard the sound-playing program takes care of obtaining the actual sound
692 }
693
694 void
695 SetCommPortDefaults ()
696 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
697 }
698
699 // [HGM] args: these three cases taken out to stay in front-end
700 void
701 SaveFontArg (FILE *f, ArgDescriptor *ad)
702 {
703   char *name;
704   int i, n = (int)(intptr_t)ad->argLoc;
705   switch(n) {
706     case 0: // CLOCK_FONT
707         name = appData.clockFont;
708       break;
709     case 1: // MESSAGE_FONT
710         name = appData.font;
711       break;
712     case 2: // COORD_FONT
713         name = appData.coordFont;
714       break;
715     default:
716       return;
717   }
718   for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
719     if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
720         fontTable[n][squareSize] = strdup(name);
721         fontValid[n][squareSize] = True;
722         break;
723   }
724   for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
725     fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
726 }
727
728 void
729 ExportSounds ()
730 { // nothing to do, as the sounds are at all times represented by their text-string names already
731 }
732
733 void
734 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
735 {       // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
736         fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
737 }
738
739 void
740 SaveColor (FILE *f, ArgDescriptor *ad)
741 {       // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
742         if(colorVariable[(int)(intptr_t)ad->argLoc])
743         fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
744 }
745
746 void
747 SaveBoardSize (FILE *f, char *name, void *addr)
748 { // wrapper to shield back-end from BoardSize & sizeInfo
749   fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
750 }
751
752 void
753 ParseCommPortSettings (char *s)
754 { // no such option in XBoard (yet)
755 }
756
757 int frameX, frameY;
758
759 #ifdef TODO_GTK
760 void
761 GetActualPlacement (Widget wg, WindowPlacement *wp)
762 {
763   XWindowAttributes winAt;
764   Window win, dummy;
765   int rx, ry;
766
767   if(!wg) return;
768
769   win = XtWindow(wg);
770   XGetWindowAttributes(xDisplay, win, &winAt); // this works, where XtGetValues on XtNx, XtNy does not!
771   XTranslateCoordinates (xDisplay, win, winAt.root, -winAt.border_width, -winAt.border_width, &rx, &ry, &dummy);
772   wp->x = rx - winAt.x;
773   wp->y = ry - winAt.y;
774   wp->height = winAt.height;
775   wp->width = winAt.width;
776   frameX = winAt.x; frameY = winAt.y; // remember to decide if windows touch
777 }
778 #endif
779
780 void
781 GetWindowCoords ()
782 { // wrapper to shield use of window handles from back-end (make addressible by number?)
783   // In XBoard this will have to wait until awareness of window parameters is implemented
784 #ifdef TODO_GTK
785   GetActualPlacement(shellWidget, &wpMain);
786   if(shellUp[EngOutDlg]) GetActualPlacement(shells[EngOutDlg], &wpEngineOutput);
787   if(shellUp[HistoryDlg]) GetActualPlacement(shells[HistoryDlg], &wpMoveHistory);
788   if(shellUp[EvalGraphDlg]) GetActualPlacement(shells[EvalGraphDlg], &wpEvalGraph);
789   if(shellUp[GameListDlg]) GetActualPlacement(shells[GameListDlg], &wpGameList);
790   if(shellUp[CommentDlg]) GetActualPlacement(shells[CommentDlg], &wpComment);
791   if(shellUp[TagsDlg]) GetActualPlacement(shells[TagsDlg], &wpTags);
792 #endif
793 }
794
795 void
796 PrintCommPortSettings (FILE *f, char *name)
797 { // This option does not exist in XBoard
798 }
799
800 void
801 EnsureOnScreen (int *x, int *y, int minX, int minY)
802 {
803   return;
804 }
805
806 int
807 MainWindowUp ()
808 { // [HGM] args: allows testing if main window is realized from back-end
809 #ifdef TODO_GTK
810   return xBoardWindow != 0;
811 #else
812   return 0;
813 #endif
814 }
815
816 void
817 PopUpStartupDialog ()
818 {  // start menu not implemented in XBoard
819 }
820
821 char *
822 ConvertToLine (int argc, char **argv)
823 {
824   static char line[128*1024], buf[1024];
825   int i;
826
827   line[0] = NULLCHAR;
828   for(i=1; i<argc; i++)
829     {
830       if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
831           && argv[i][0] != '{' )
832         snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
833       else
834         snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
835       strncat(line, buf, 128*1024 - strlen(line) - 1 );
836     }
837
838   line[strlen(line)-1] = NULLCHAR;
839   return line;
840 }
841
842 //--------------------------------------------------------------------------------------------
843
844 void
845 ResizeBoardWindow (int w, int h, int inhibit)
846 {
847 #ifdef TODO_GTK
848     w += marginW + 1; // [HGM] not sure why the +1 is (sometimes) needed...
849     h += marginH;
850     shellArgs[0].value = w;
851     shellArgs[1].value = h;
852     shellArgs[4].value = shellArgs[2].value = w;
853     shellArgs[5].value = shellArgs[3].value = h;
854     XtSetValues(shellWidget, &shellArgs[0], inhibit ? 6 : 2);
855
856     XSync(xDisplay, False);
857 #endif
858 }
859
860 #ifdef TODO_GTK
861 static int
862 MakeOneColor (char *name, Pixel *color)
863 {
864     XrmValue vFrom, vTo;
865     if (!appData.monoMode) {
866         vFrom.addr = (caddr_t) name;
867         vFrom.size = strlen(name);
868         XtConvert(shellWidget, XtRString, &vFrom, XtRPixel, &vTo);
869         if (vTo.addr == NULL) {
870           appData.monoMode = True;
871           return True;
872         } else {
873           *color = *(Pixel *) vTo.addr;
874         }
875     }
876     return False;
877 }
878 #endif
879
880 int
881 MakeColors ()
882 {   // [HGM] taken out of main(), so it can be called from BoardOptions dialog
883     int forceMono = False;
884
885 #ifdef TODO_GTK
886     if (appData.lowTimeWarning)
887         forceMono |= MakeOneColor(appData.lowTimeWarningColor, &lowTimeWarningColor);
888     if(appData.dialogColor[0]) MakeOneColor(appData.dialogColor, &dialogColor);
889     if(appData.buttonColor[0]) MakeOneColor(appData.buttonColor, &buttonColor);
890 #endif
891
892     return forceMono;
893 }
894
895 void
896 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
897 {   // detervtomine what fonts to use, and create them
898 #ifdef TODO_GTK
899     XrmValue vTo;
900     XrmDatabase xdb;
901
902     if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
903         appData.clockFont = fontTable[CLOCK_FONT][squareSize];
904     if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
905         appData.font = fontTable[MESSAGE_FONT][squareSize];
906     if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
907         appData.coordFont = fontTable[COORD_FONT][squareSize];
908
909 #if ENABLE_NLS
910     appData.font = InsertPxlSize(appData.font, fontPxlSize);
911     appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
912     appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
913     fontSet = CreateFontSet(appData.font);
914     clockFontSet = CreateFontSet(appData.clockFont);
915     {
916       /* For the coordFont, use the 0th font of the fontset. */
917       XFontSet coordFontSet = CreateFontSet(appData.coordFont);
918       XFontStruct **font_struct_list;
919       XFontSetExtents *fontSize;
920       char **font_name_list;
921       XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
922       coordFontID = XLoadFont(xDisplay, font_name_list[0]);
923       coordFontStruct = XQueryFont(xDisplay, coordFontID);
924       fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
925       textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
926     }
927 #else
928     appData.font = FindFont(appData.font, fontPxlSize);
929     appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
930     appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
931     clockFontID = XLoadFont(xDisplay, appData.clockFont);
932     clockFontStruct = XQueryFont(xDisplay, clockFontID);
933     coordFontID = XLoadFont(xDisplay, appData.coordFont);
934     coordFontStruct = XQueryFont(xDisplay, coordFontID);
935     // textHeight in !NLS mode!
936 #endif
937     countFontID = coordFontID;  // [HGM] holdings
938     countFontStruct = coordFontStruct;
939
940     xdb = XtDatabase(xDisplay);
941 #if ENABLE_NLS
942     XrmPutLineResource(&xdb, "*international: True");
943     vTo.size = sizeof(XFontSet);
944     vTo.addr = (XtPointer) &fontSet;
945     XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
946 #else
947     XrmPutStringResource(&xdb, "*font", appData.font);
948 #endif
949 #endif
950 }
951
952 char *
953 PrintArg (ArgType t)
954 {
955   char *p="";
956   switch(t) {
957     case ArgZ:
958     case ArgInt:      p = " N"; break;
959     case ArgString:   p = " STR"; break;
960     case ArgBoolean:  p = " TF"; break;
961     case ArgSettingsFilename:
962     case ArgFilename: p = " FILE"; break;
963     case ArgX:        p = " Nx"; break;
964     case ArgY:        p = " Ny"; break;
965     case ArgAttribs:  p = " TEXTCOL"; break;
966     case ArgColor:    p = " COL"; break;
967     case ArgFont:     p = " FONT"; break;
968     case ArgBoardSize: p = " SIZE"; break;
969     case ArgFloat: p = " FLOAT"; break;
970     case ArgTrue:
971     case ArgFalse:
972     case ArgTwo:
973     case ArgNone:
974     case ArgCommSettings:
975       break;
976   }
977   return p;
978 }
979
980 void
981 PrintOptions ()
982 {
983   char buf[MSG_SIZ];
984   int len=0;
985   ArgDescriptor *q, *p = argDescriptors+5;
986   printf("\nXBoard accepts the following options:\n"
987          "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
988          " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
989          " SIZE = board-size spec(s)\n"
990          " Within parentheses are short forms, or options to set to true or false.\n"
991          " Persistent options (saved in the settings file) are marked with *)\n\n");
992   while(p->argName) {
993     if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
994     snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
995     if(p->save) strcat(buf+len, "*");
996     for(q=p+1; q->argLoc == p->argLoc; q++) {
997       if(q->argName[0] == '-') continue;
998       strcat(buf+len, q == p+1 ? " (" : " ");
999       sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
1000     }
1001     if(q != p+1) strcat(buf+len, ")");
1002     len = strlen(buf);
1003     if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
1004     p = q;
1005   }
1006   if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
1007 }
1008
1009 int
1010 main (int argc, char **argv)
1011 {
1012     int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
1013 #ifdef TODO_GTK
1014     XSetWindowAttributes window_attributes;
1015     Arg args[16];
1016     Dimension boardWidth, boardHeight, w, h;
1017 #else
1018 #endif
1019     int boardWidth, boardHeight, w, h;
1020     char *p;
1021     int forceMono = False;
1022     GError *gtkerror=NULL;
1023
1024     srandom(time(0)); // [HGM] book: make random truly random
1025
1026     setbuf(stdout, NULL);
1027     setbuf(stderr, NULL);
1028     debugFP = stderr;
1029
1030     if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
1031         printf("%s version %s\n", PACKAGE_NAME, PACKAGE_VERSION);
1032         exit(0);
1033     }
1034
1035     if(argc > 1 && !strcmp(argv[1], "--help" )) {
1036         PrintOptions();
1037         exit(0);
1038     }
1039
1040     /* set up GTK */
1041     gtk_init (&argc, &argv);
1042
1043     programName = strrchr(argv[0], '/');
1044     if (programName == NULL)
1045       programName = argv[0];
1046     else
1047       programName++;
1048
1049 #ifdef ENABLE_NLS
1050 //    if (appData.debugMode) {
1051 //      fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
1052 //    }
1053
1054     bindtextdomain(PACKAGE, LOCALEDIR);
1055     textdomain(PACKAGE);
1056 #endif
1057
1058     appData.boardSize = "";
1059     InitAppData(ConvertToLine(argc, argv));
1060     p = getenv("HOME");
1061     if (p == NULL) p = "/tmp";
1062     i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
1063     gameCopyFilename = (char*) malloc(i);
1064     gamePasteFilename = (char*) malloc(i);
1065     snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
1066     snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
1067
1068     { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
1069         static char buf[MSG_SIZ];
1070         EscapeExpand(buf, appData.firstInitString);
1071         appData.firstInitString = strdup(buf);
1072         EscapeExpand(buf, appData.secondInitString);
1073         appData.secondInitString = strdup(buf);
1074         EscapeExpand(buf, appData.firstComputerString);
1075         appData.firstComputerString = strdup(buf);
1076         EscapeExpand(buf, appData.secondComputerString);
1077         appData.secondComputerString = strdup(buf);
1078     }
1079
1080     if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
1081         chessDir = ".";
1082     } else {
1083         if (chdir(chessDir) != 0) {
1084             fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
1085             perror(chessDir);
1086             exit(1);
1087         }
1088     }
1089
1090     if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
1091         /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
1092         if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL)  {
1093            printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
1094            exit(errno);
1095         }
1096         setbuf(debugFP, NULL);
1097     }
1098
1099 #if ENABLE_NLS
1100     if (appData.debugMode) {
1101       fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
1102     }
1103 #endif
1104
1105     /* [HGM,HR] make sure board size is acceptable */
1106     if(appData.NrFiles > BOARD_FILES ||
1107        appData.NrRanks > BOARD_RANKS   )
1108          DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
1109
1110 #if !HIGHDRAG
1111     /* This feature does not work; animation needs a rewrite */
1112     appData.highlightDragging = FALSE;
1113 #endif
1114     InitBackEnd1();
1115
1116         gameInfo.variant = StringToVariant(appData.variant);
1117         InitPosition(FALSE);
1118
1119 #ifdef TODO_GTK
1120     /* GTK */
1121     builder = gtk_builder_new();
1122     filename = get_glade_filename ("mainboard.glade");
1123     if(! gtk_builder_add_from_file (builder, filename, &gtkerror) )
1124       {
1125       if(gtkerror)
1126         printf ("Error: %d %s\n",gtkerror->code,gtkerror->message);
1127       }
1128     mainwindow = GTK_WIDGET(gtk_builder_get_object (builder, "mainwindow"));
1129
1130     shellWidget =
1131       XtAppInitialize(&appContext, "XBoard", shellOptions,
1132                       XtNumber(shellOptions),
1133                       &argc, argv, xboardResources, NULL, 0);
1134
1135     XtGetApplicationResources(shellWidget, (XtPointer) &appData,
1136                               clientResources, XtNumber(clientResources),
1137                               NULL, 0);
1138
1139     xDisplay = XtDisplay(shellWidget);
1140     xScreen = DefaultScreen(xDisplay);
1141     wm_delete_window = XInternAtom(xDisplay, "WM_DELETE_WINDOW", True);
1142 #endif
1143
1144     /*
1145      * determine size, based on supplied or remembered -size, or screen size
1146      */
1147     if (isdigit(appData.boardSize[0])) {
1148         i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
1149                    &lineGap, &clockFontPxlSize, &coordFontPxlSize,
1150                    &fontPxlSize, &smallLayout, &tinyLayout);
1151         if (i == 0) {
1152             fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
1153                     programName, appData.boardSize);
1154             exit(2);
1155         }
1156         if (i < 7) {
1157             /* Find some defaults; use the nearest known size */
1158             SizeDefaults *szd, *nearest;
1159             int distance = 99999;
1160             nearest = szd = sizeDefaults;
1161             while (szd->name != NULL) {
1162                 if (abs(szd->squareSize - squareSize) < distance) {
1163                     nearest = szd;
1164                     distance = abs(szd->squareSize - squareSize);
1165                     if (distance == 0) break;
1166                 }
1167                 szd++;
1168             }
1169             if (i < 2) lineGap = nearest->lineGap;
1170             if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
1171             if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
1172             if (i < 5) fontPxlSize = nearest->fontPxlSize;
1173             if (i < 6) smallLayout = nearest->smallLayout;
1174             if (i < 7) tinyLayout = nearest->tinyLayout;
1175         }
1176     } else {
1177         SizeDefaults *szd = sizeDefaults;
1178         if (*appData.boardSize == NULLCHAR) {
1179 #ifdef TODO_GTK
1180             while (DisplayWidth(xDisplay, xScreen) < szd->minScreenSize ||
1181                    DisplayHeight(xDisplay, xScreen) < szd->minScreenSize) {
1182               szd++;
1183             }
1184 #else
1185             GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow));
1186             guint screenwidth = gdk_screen_get_width(screen);
1187             guint screenheight = gdk_screen_get_height(screen);
1188             while (screenwidth < szd->minScreenSize ||
1189                    screenheight < szd->minScreenSize) {
1190               szd++;
1191             }
1192 #endif
1193             if (szd->name == NULL) szd--;
1194             appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
1195         } else {
1196             while (szd->name != NULL &&
1197                    StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
1198             if (szd->name == NULL) {
1199                 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
1200                         programName, appData.boardSize);
1201                 exit(2);
1202             }
1203         }
1204         squareSize = szd->squareSize;
1205         lineGap = szd->lineGap;
1206         clockFontPxlSize = szd->clockFontPxlSize;
1207         coordFontPxlSize = szd->coordFontPxlSize;
1208         fontPxlSize = szd->fontPxlSize;
1209         smallLayout = szd->smallLayout;
1210         tinyLayout = szd->tinyLayout;
1211         // [HGM] font: use defaults from settings file if available and not overruled
1212     }
1213
1214     defaultLineGap = lineGap;
1215     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
1216
1217     /* [HR] height treated separately (hacked) */
1218     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
1219     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1220
1221     /*
1222      * Determine what fonts to use.
1223      */
1224 #ifdef TODO_GTK
1225     InitializeFonts(clockFontPxlSize, coordFontPxlSize, fontPxlSize);
1226 #endif
1227
1228     /*
1229      * Detect if there are not enough colors available and adapt.
1230      */
1231 #ifdef TODO_GTK
1232     if (DefaultDepth(xDisplay, xScreen) <= 2) {
1233       appData.monoMode = True;
1234     }
1235 #endif
1236
1237     forceMono = MakeColors();
1238
1239     if (forceMono) {
1240       fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1241               programName);
1242         appData.monoMode = True;
1243     }
1244
1245     if (appData.monoMode && appData.debugMode) {
1246 #ifdef TODO_GTK
1247         fprintf(stderr, _("white pixel = 0x%lx, black pixel = 0x%lx\n"),
1248                 (unsigned long) XWhitePixel(xDisplay, xScreen),
1249                 (unsigned long) XBlackPixel(xDisplay, xScreen));
1250 #endif
1251     }
1252
1253     ParseIcsTextColors();
1254
1255 #ifdef TODO_GTK
1256     XtAppAddActions(appContext, boardActions, XtNumber(boardActions));
1257 #endif
1258
1259     /*
1260      * widget hierarchy
1261      */
1262     if (tinyLayout) {
1263         layoutName = "tinyLayout";
1264     } else if (smallLayout) {
1265         layoutName = "smallLayout";
1266     } else {
1267         layoutName = "normalLayout";
1268     }
1269
1270     optList = BoardPopUp(squareSize, lineGap, (void*)
1271 #ifdef TODO_GTK
1272 #if ENABLE_NLS
1273                                                 &clockFontSet);
1274 #else
1275                                                 clockFontStruct);
1276 #endif
1277 #else
1278 0);
1279 #endif
1280     InitDrawingHandle(optList + W_BOARD);
1281     currBoard        = &optList[W_BOARD];
1282     boardWidget      = optList[W_BOARD].handle;
1283     menuBarWidget    = optList[W_MENU].handle;
1284     dropMenu         = optList[W_DROP].handle;
1285     titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1286 #ifdef TODO_GTK
1287     formWidget  = XtParent(boardWidget);
1288     XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1289     XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1290     XtGetValues(optList[W_WHITE].handle, args, 2);
1291     if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1292       XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1293       XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1294       XtGetValues(optList[W_PAUSE].handle, args, 2);
1295     }
1296 #endif
1297
1298 #ifdef TODO_GTK
1299     xBoardWindow = XtWindow(boardWidget);
1300 #endif
1301
1302     // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1303     //       not need to go into InitDrawingSizes().
1304
1305     InitMenuMarkers();
1306
1307     /*
1308      * Create an icon.
1309      */
1310 #ifdef TODO_GTK
1311     ReadBitmap(&wIconPixmap, "icon_white.bm",
1312                icon_white_bits, icon_white_width, icon_white_height);
1313     ReadBitmap(&bIconPixmap, "icon_black.bm",
1314                icon_black_bits, icon_black_width, icon_black_height);
1315     iconPixmap = wIconPixmap;
1316     i = 0;
1317     XtSetArg(args[i], XtNiconPixmap, iconPixmap);  i++;
1318     XtSetValues(shellWidget, args, i);
1319 #endif
1320
1321     /*
1322      * Create a cursor for the board widget.
1323      */
1324 #ifdef TODO_GTK
1325     window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1326     XChangeWindowAttributes(xDisplay, xBoardWindow,
1327                             CWCursor, &window_attributes);
1328 #endif
1329
1330     /*
1331      * Inhibit shell resizing.
1332      */
1333 #ifdef TODO_GTK
1334     shellArgs[0].value = (XtArgVal) &w;
1335     shellArgs[1].value = (XtArgVal) &h;
1336     XtGetValues(shellWidget, shellArgs, 2);
1337     shellArgs[4].value = shellArgs[2].value = w;
1338     shellArgs[5].value = shellArgs[3].value = h;
1339 //    XtSetValues(shellWidget, &shellArgs[2], 4);
1340 #endif
1341     marginW =  w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1342     marginH =  h - boardHeight;
1343
1344 #ifdef TODO_GTK
1345     CatchDeleteWindow(shellWidget, "QuitProc");
1346 #endif
1347
1348     CreateAnyPieces();
1349     CreateGrid();
1350
1351     if(appData.logoSize)
1352     {   // locate and read user logo
1353         char buf[MSG_SIZ];
1354         snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1355         ASSIGN(userLogo, buf);
1356     }
1357
1358     if (appData.animate || appData.animateDragging)
1359       CreateAnimVars();
1360
1361 #ifdef TODO_GTK
1362     XtAugmentTranslations(formWidget,
1363                           XtParseTranslationTable(globalTranslations));
1364
1365     XtAddEventHandler(formWidget, KeyPressMask, False,
1366                       (XtEventHandler) MoveTypeInProc, NULL);
1367     XtAddEventHandler(shellWidget, StructureNotifyMask, False,
1368                       (XtEventHandler) EventProc, NULL);
1369 #endif
1370
1371     /* [AS] Restore layout */
1372     if( wpMoveHistory.visible ) {
1373       HistoryPopUp();
1374     }
1375
1376     if( wpEvalGraph.visible )
1377       {
1378         EvalGraphPopUp();
1379       };
1380
1381     if( wpEngineOutput.visible ) {
1382       EngineOutputPopUp();
1383     }
1384
1385     InitBackEnd2();
1386
1387     if (errorExitStatus == -1) {
1388         if (appData.icsActive) {
1389             /* We now wait until we see "login:" from the ICS before
1390                sending the logon script (problems with timestamp otherwise) */
1391             /*ICSInitScript();*/
1392             if (appData.icsInputBox) ICSInputBoxPopUp();
1393         }
1394
1395     #ifdef SIGWINCH
1396     signal(SIGWINCH, TermSizeSigHandler);
1397     #endif
1398         signal(SIGINT, IntSigHandler);
1399         signal(SIGTERM, IntSigHandler);
1400         if (*appData.cmailGameName != NULLCHAR) {
1401             signal(SIGUSR1, CmailSigHandler);
1402         }
1403     }
1404
1405     gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1406     InitPosition(TRUE);
1407     UpdateLogos(TRUE);
1408 //    XtSetKeyboardFocus(shellWidget, formWidget);
1409 #ifdef TODO_GTK
1410     XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1411 #endif
1412
1413     /* check for GTK events and process them */
1414 //    gtk_main();
1415 while(1) {
1416 gtk_main_iteration();
1417 }
1418
1419     if (appData.debugMode) fclose(debugFP); // [DM] debug
1420     return 0;
1421 }
1422
1423 RETSIGTYPE
1424 TermSizeSigHandler (int sig)
1425 {
1426     update_ics_width();
1427 }
1428
1429 RETSIGTYPE
1430 IntSigHandler (int sig)
1431 {
1432     ExitEvent(sig);
1433 }
1434
1435 RETSIGTYPE
1436 CmailSigHandler (int sig)
1437 {
1438     int dummy = 0;
1439     int error;
1440
1441     signal(SIGUSR1, SIG_IGN);   /* suspend handler     */
1442
1443     /* Activate call-back function CmailSigHandlerCallBack()             */
1444     OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1445
1446     signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1447 }
1448
1449 void
1450 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1451 {
1452     BoardToTop();
1453     ReloadCmailMsgEvent(TRUE);  /* Reload cmail msg  */
1454 }
1455 /**** end signal code ****/
1456
1457
1458 #define Abs(n) ((n)<0 ? -(n) : (n))
1459
1460 #ifdef ENABLE_NLS
1461 char *
1462 InsertPxlSize (char *pattern, int targetPxlSize)
1463 {
1464     char *base_fnt_lst, strInt[12], *p, *q;
1465     int alternatives, i, len, strIntLen;
1466
1467     /*
1468      * Replace the "*" (if present) in the pixel-size slot of each
1469      * alternative with the targetPxlSize.
1470      */
1471     p = pattern;
1472     alternatives = 1;
1473     while ((p = strchr(p, ',')) != NULL) {
1474       alternatives++;
1475       p++;
1476     }
1477     snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1478     strIntLen = strlen(strInt);
1479     base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1480
1481     p = pattern;
1482     q = base_fnt_lst;
1483     while (alternatives--) {
1484       char *comma = strchr(p, ',');
1485       for (i=0; i<14; i++) {
1486         char *hyphen = strchr(p, '-');
1487         if (!hyphen) break;
1488         if (comma && hyphen > comma) break;
1489         len = hyphen + 1 - p;
1490         if (i == 7 && *p == '*' && len == 2) {
1491           p += len;
1492           memcpy(q, strInt, strIntLen);
1493           q += strIntLen;
1494           *q++ = '-';
1495         } else {
1496           memcpy(q, p, len);
1497           p += len;
1498           q += len;
1499         }
1500       }
1501       if (!comma) break;
1502       len = comma + 1 - p;
1503       memcpy(q, p, len);
1504       p += len;
1505       q += len;
1506     }
1507     strcpy(q, p);
1508
1509     return base_fnt_lst;
1510 }
1511
1512 #ifdef TODO_GTK
1513 XFontSet
1514 CreateFontSet (char *base_fnt_lst)
1515 {
1516     XFontSet fntSet;
1517     char **missing_list;
1518     int missing_count;
1519     char *def_string;
1520
1521     fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1522                             &missing_list, &missing_count, &def_string);
1523     if (appData.debugMode) {
1524       int i, count;
1525       XFontStruct **font_struct_list;
1526       char **font_name_list;
1527       fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1528       if (fntSet) {
1529         fprintf(debugFP, " got list %s, locale %s\n",
1530                 XBaseFontNameListOfFontSet(fntSet),
1531                 XLocaleOfFontSet(fntSet));
1532         count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1533         for (i = 0; i < count; i++) {
1534           fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1535         }
1536       }
1537       for (i = 0; i < missing_count; i++) {
1538         fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1539       }
1540     }
1541     if (fntSet == NULL) {
1542       fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1543       exit(2);
1544     }
1545     return fntSet;
1546 }
1547 #endif
1548 #else // not ENABLE_NLS
1549 /*
1550  * Find a font that matches "pattern" that is as close as
1551  * possible to the targetPxlSize.  Prefer fonts that are k
1552  * pixels smaller to fonts that are k pixels larger.  The
1553  * pattern must be in the X Consortium standard format,
1554  * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1555  * The return value should be freed with XtFree when no
1556  * longer needed.
1557  */
1558 char *
1559 FindFont (char *pattern, int targetPxlSize)
1560 {
1561     char **fonts, *p, *best, *scalable, *scalableTail;
1562     int i, j, nfonts, minerr, err, pxlSize;
1563
1564 #ifdef TODO_GTK
1565     fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1566     if (nfonts < 1) {
1567         fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1568                 programName, pattern);
1569         exit(2);
1570     }
1571
1572     best = fonts[0];
1573     scalable = NULL;
1574     minerr = 999999;
1575     for (i=0; i<nfonts; i++) {
1576         j = 0;
1577         p = fonts[i];
1578         if (*p != '-') continue;
1579         while (j < 7) {
1580             if (*p == NULLCHAR) break;
1581             if (*p++ == '-') j++;
1582         }
1583         if (j < 7) continue;
1584         pxlSize = atoi(p);
1585         if (pxlSize == 0) {
1586             scalable = fonts[i];
1587             scalableTail = p;
1588         } else {
1589             err = pxlSize - targetPxlSize;
1590             if (Abs(err) < Abs(minerr) ||
1591                 (minerr > 0 && err < 0 && -err == minerr)) {
1592                 best = fonts[i];
1593                 minerr = err;
1594             }
1595         }
1596     }
1597     if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1598         /* If the error is too big and there is a scalable font,
1599            use the scalable font. */
1600         int headlen = scalableTail - scalable;
1601         p = (char *) XtMalloc(strlen(scalable) + 10);
1602         while (isdigit(*scalableTail)) scalableTail++;
1603         sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1604     } else {
1605         p = (char *) XtMalloc(strlen(best) + 2);
1606         safeStrCpy(p, best, strlen(best)+1 );
1607     }
1608     if (appData.debugMode) {
1609         fprintf(debugFP, _("resolved %s at pixel size %d\n  to %s\n"),
1610                 pattern, targetPxlSize, p);
1611     }
1612     XFreeFontNames(fonts);
1613 #endif
1614     return p;
1615 }
1616 #endif
1617
1618 void
1619 EnableNamedMenuItem (char *menuRef, int state)
1620 {
1621     MenuItem *item = MenuNameToItem(menuRef);
1622
1623     if(item) gtk_widget_set_sensitive(item->handle, state);
1624 }
1625
1626 void
1627 EnableButtonBar (int state)
1628 {
1629 #ifdef TODO_GTK
1630     XtSetSensitive(optList[W_BUTTON].handle, state);
1631 #endif
1632 }
1633
1634
1635 void
1636 SetMenuEnables (Enables *enab)
1637 {
1638   while (enab->name != NULL) {
1639     EnableNamedMenuItem(enab->name, enab->value);
1640     enab++;
1641   }
1642 }
1643
1644 #ifdef TODO_GTK
1645 void
1646 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1647 {   // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1648     MenuItem *item;
1649     if(*nprms == 0) return;
1650     item = MenuNameToItem(prms[0]);
1651     if(item) ((MenuProc *) item->proc) ();
1652 }
1653 #endif
1654
1655 void
1656 SetupDropMenu ()
1657 {
1658 #ifdef TODO_GTK
1659     int i, j, count;
1660     char label[32];
1661     Arg args[16];
1662     Widget entry;
1663     char* p;
1664
1665     for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1666         entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1667         p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1668                    dmEnables[i].piece);
1669         XtSetSensitive(entry, p != NULL || !appData.testLegality
1670                        /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1671                                        && !appData.icsActive));
1672         count = 0;
1673         while (p && *p++ == dmEnables[i].piece) count++;
1674         snprintf(label, sizeof(label), "%s  %d", dmEnables[i].widget, count);
1675         j = 0;
1676         XtSetArg(args[j], XtNlabel, label); j++;
1677         XtSetValues(entry, args, j);
1678     }
1679 #endif
1680 }
1681
1682 static void
1683 do_flash_delay (unsigned long msec)
1684 {
1685     TimeDelay(msec);
1686 }
1687
1688 void
1689 FlashDelay (int flash_delay)
1690 {
1691 #ifdef TODO_GTK
1692         XSync(xDisplay, False);
1693         if(flash_delay) do_flash_delay(flash_delay);
1694 #endif
1695 }
1696
1697 double
1698 Fraction (int x, int start, int stop)
1699 {
1700    double f = ((double) x - start)/(stop - start);
1701    if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1702    return f;
1703 }
1704
1705 static WindowPlacement wpNew;
1706
1707 #ifdef TODO_GTK
1708 void
1709 CoDrag (Widget sh, WindowPlacement *wp)
1710 {
1711     Arg args[16];
1712     int j=0, touch=0, fudge = 2;
1713     GetActualPlacement(sh, wp);
1714     if(abs(wpMain.x + wpMain.width + 2*frameX - wp->x)         < fudge) touch = 1; else // right touch
1715     if(abs(wp->x + wp->width + 2*frameX - wpMain.x)            < fudge) touch = 2; else // left touch
1716     if(abs(wpMain.y + wpMain.height + frameX + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1717     if(abs(wp->y + wp->height + frameX + frameY - wpMain.y)    < fudge) touch = 4;      // top touch
1718     if(!touch ) return; // only windows that touch co-move
1719     if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1720         int heightInc = wpNew.height - wpMain.height;
1721         double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1722         double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1723         wp->y += fracTop * heightInc;
1724         heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1725         if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1726     } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1727         int widthInc = wpNew.width - wpMain.width;
1728         double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1729         double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1730         wp->y += fracLeft * widthInc;
1731         widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1732         if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1733     }
1734     wp->x += wpNew.x - wpMain.x;
1735     wp->y += wpNew.y - wpMain.y;
1736     if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1737     if(touch == 3) wp->y += wpNew.height - wpMain.height;
1738 #ifdef TODO_GTK
1739     XtSetArg(args[j], XtNx, wp->x); j++;
1740     XtSetArg(args[j], XtNy, wp->y); j++;
1741     XtSetValues(sh, args, j);
1742 #endif
1743 }
1744
1745 void
1746 ReSize (WindowPlacement *wp)
1747 {
1748         int sqx, sqy, w, h;
1749         if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1750         sqx = (wp->width  - lineGap - marginW) / BOARD_WIDTH - lineGap;
1751         sqy = (wp->height - lineGap - marginH) / BOARD_HEIGHT - lineGap;
1752         if(sqy < sqx) sqx = sqy;
1753         if(sqx != squareSize) {
1754             squareSize = sqx; // adopt new square size
1755             CreatePNGPieces(); // make newly scaled pieces
1756             InitDrawingSizes(0, 0); // creates grid etc.
1757         } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1758         w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1759         h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1760         if(optList[W_BOARD].max   > w) optList[W_BOARD].max = w;
1761         if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1762 }
1763
1764 #ifdef TODO_GTK
1765 static XtIntervalId delayedDragID = 0;
1766 #else
1767 static int delayedDragID = 0;
1768 #endif
1769
1770 void
1771 DragProc ()
1772 {
1773         static int busy;
1774         if(busy) return;
1775
1776         busy = 1;
1777         GetActualPlacement(shellWidget, &wpNew);
1778         if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1779            wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1780             busy = 0; return; // false alarm
1781         }
1782         ReSize(&wpNew);
1783         if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1784         if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1785         if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1786         if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1787         wpMain = wpNew;
1788         DrawPosition(True, NULL);
1789         delayedDragID = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1790         busy = 0;
1791 }
1792 #endif
1793
1794 void
1795 DelayedDrag ()
1796 {
1797 #ifdef TODO_GTK
1798     if(delayedDragID) XtRemoveTimeOut(delayedDragID); // cancel pending
1799     delayedDragID =
1800       XtAppAddTimeOut(appContext, 200, (XtTimerCallbackProc) DragProc, (XtPointer) 0); // and schedule new one 50 msec later
1801 #endif
1802 }
1803
1804 #ifdef TODO_GTK
1805 void
1806 EventProc (Widget widget, caddr_t unused, XEvent *event)
1807 {
1808     if(XtIsRealized(widget) && event->type == ConfigureNotify || appData.useStickyWindows)
1809         DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1810 }
1811 #endif
1812
1813 /*
1814  * event handler for redrawing the board
1815  */
1816 #ifdef TODO_GTK
1817 void
1818 DrawPositionProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1819 {
1820     DrawPosition(True, NULL);
1821 }
1822 #endif
1823
1824
1825 #ifdef TODO_GTK
1826 void
1827 HandlePV (Widget w, XEvent * event, String * params, Cardinal * nParams)
1828 {   // [HGM] pv: walk PV
1829     MovePV(event->xmotion.x, event->xmotion.y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
1830 }
1831 #endif
1832
1833 static int savedIndex;  /* gross that this is global */
1834
1835 #ifdef TODO_GTK
1836 void
1837 CommentClick (Widget w, XEvent * event, String * params, Cardinal * nParams)
1838 {
1839         String val;
1840         XawTextPosition index, dummy;
1841         Arg arg;
1842
1843         XawTextGetSelectionPos(w, &index, &dummy);
1844         XtSetArg(arg, XtNstring, &val);
1845         XtGetValues(w, &arg, 1);
1846         ReplaceComment(savedIndex, val);
1847         if(savedIndex != currentMove) ToNrEvent(savedIndex);
1848         LoadVariation( index, val ); // [HGM] also does the actual moving to it, now
1849 }
1850 #endif
1851
1852 void
1853 EditCommentPopUp (int index, char *title, char *text)
1854 {
1855     savedIndex = index;
1856     if (text == NULL) text = "";
1857     NewCommentPopup(title, text, index);
1858 }
1859
1860 void
1861 CommentPopUp (char *title, char *text)
1862 {
1863     savedIndex = currentMove; // [HGM] vari
1864     NewCommentPopup(title, text, currentMove);
1865 }
1866
1867 void
1868 CommentPopDown ()
1869 {
1870     PopDown(CommentDlg);
1871 }
1872
1873
1874 /* Disable all user input other than deleting the window */
1875 static int frozen = 0;
1876
1877 void
1878 FreezeUI ()
1879 {
1880   if (frozen) return;
1881   /* Grab by a widget that doesn't accept input */
1882   gtk_grab_add(optList[W_MESSG].handle);
1883   frozen = 1;
1884 }
1885
1886 /* Undo a FreezeUI */
1887 void
1888 ThawUI ()
1889 {
1890   if (!frozen) return;
1891   gtk_grab_remove(optList[W_MESSG].handle);
1892   frozen = 0;
1893 }
1894
1895 void
1896 ModeHighlight ()
1897 {
1898     static int oldPausing = FALSE;
1899     static GameMode oldmode = (GameMode) -1;
1900     char *wname;
1901 #ifdef TODO_GTK
1902     Arg args[16];
1903
1904     if (!boardWidget || !XtIsRealized(boardWidget)) return;
1905
1906     if (pausing != oldPausing) {
1907         oldPausing = pausing;
1908         MarkMenuItem("Mode.Pause", pausing);
1909
1910         if (appData.showButtonBar) {
1911           /* Always toggle, don't set.  Previous code messes up when
1912              invoked while the button is pressed, as releasing it
1913              toggles the state again. */
1914           {
1915             Pixel oldbg, oldfg;
1916             XtSetArg(args[0], XtNbackground, &oldbg);
1917             XtSetArg(args[1], XtNforeground, &oldfg);
1918             XtGetValues(optList[W_PAUSE].handle,
1919                         args, 2);
1920             XtSetArg(args[0], XtNbackground, oldfg);
1921             XtSetArg(args[1], XtNforeground, oldbg);
1922           }
1923           XtSetValues(optList[W_PAUSE].handle, args, 2);
1924         }
1925     }
1926 #endif
1927
1928     wname = ModeToWidgetName(oldmode);
1929     if (wname != NULL) {
1930         MarkMenuItem(wname, False);
1931     }
1932     wname = ModeToWidgetName(gameMode);
1933     if (wname != NULL) {
1934         MarkMenuItem(wname, True);
1935     }
1936     oldmode = gameMode;
1937     MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1938
1939     /* Maybe all the enables should be handled here, not just this one */
1940     EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1941
1942     DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1943 }
1944
1945
1946 /*
1947  * Button/menu procedures
1948  */
1949
1950 #ifdef TODO_GTK
1951 /* this variable is shared between CopyPositionProc and SendPositionSelection */
1952 char *selected_fen_position=NULL;
1953
1954 Boolean
1955 SendPositionSelection (Widget w, Atom *selection, Atom *target,
1956                        Atom *type_return, XtPointer *value_return,
1957                        unsigned long *length_return, int *format_return)
1958 {
1959   char *selection_tmp;
1960
1961 //  if (!selected_fen_position) return False; /* should never happen */
1962   if (*target == XA_STRING || *target == XA_UTF8_STRING(xDisplay)){
1963    if (!selected_fen_position) { // since it never happens, we use it for indicating a game is being sent
1964     FILE* f = fopen(gameCopyFilename, "r"); // This code, taken from SendGameSelection, now merges the two
1965     long len;
1966     size_t count;
1967     if (f == NULL) return False;
1968     fseek(f, 0, 2);
1969     len = ftell(f);
1970     rewind(f);
1971     selection_tmp = XtMalloc(len + 1);
1972     count = fread(selection_tmp, 1, len, f);
1973     fclose(f);
1974     if (len != count) {
1975       XtFree(selection_tmp);
1976       return False;
1977     }
1978     selection_tmp[len] = NULLCHAR;
1979    } else {
1980     /* note: since no XtSelectionDoneProc was registered, Xt will
1981      * automatically call XtFree on the value returned.  So have to
1982      * make a copy of it allocated with XtMalloc */
1983     selection_tmp= XtMalloc(strlen(selected_fen_position)+16);
1984     safeStrCpy(selection_tmp, selected_fen_position, strlen(selected_fen_position)+16 );
1985    }
1986
1987     *value_return=selection_tmp;
1988     *length_return=strlen(selection_tmp);
1989     *type_return=*target;
1990     *format_return = 8; /* bits per byte */
1991     return True;
1992   } else if (*target == XA_TARGETS(xDisplay)) {
1993     Atom *targets_tmp = (Atom *) XtMalloc(2 * sizeof(Atom));
1994     targets_tmp[0] = XA_UTF8_STRING(xDisplay);
1995     targets_tmp[1] = XA_STRING;
1996     *value_return = targets_tmp;
1997     *type_return = XA_ATOM;
1998     *length_return = 2;
1999 #if 0
2000     // This code leads to a read of value_return out of bounds on 64-bit systems.
2001     // Other code which I have seen always sets *format_return to 32 independent of
2002     // sizeof(Atom) without adjusting *length_return. For instance see TextConvertSelection()
2003     // at http://cgit.freedesktop.org/xorg/lib/libXaw/tree/src/Text.c -- BJ
2004     *format_return = 8 * sizeof(Atom);
2005     if (*format_return > 32) {
2006       *length_return *= *format_return / 32;
2007       *format_return = 32;
2008     }
2009 #else
2010     *format_return = 32;
2011 #endif
2012     return True;
2013   } else {
2014     return False;
2015   }
2016 }
2017 #endif
2018
2019 /* note: when called from menu all parameters are NULL, so no clue what the
2020  * Widget which was clicked on was, or what the click event was
2021  */
2022 void
2023 CopySomething (char *src)
2024 {
2025 #ifdef TODO_GTK
2026     selected_fen_position = src;
2027     /*
2028      * Set both PRIMARY (the selection) and CLIPBOARD, since we don't
2029      * have a notion of a position that is selected but not copied.
2030      * See http://www.freedesktop.org/wiki/Specifications/ClipboardsWiki
2031      */
2032     XtOwnSelection(menuBarWidget, XA_PRIMARY,
2033                    CurrentTime,
2034                    SendPositionSelection,
2035                    NULL/* lose_ownership_proc */ ,
2036                    NULL/* transfer_done_proc */);
2037     XtOwnSelection(menuBarWidget, XA_CLIPBOARD(xDisplay),
2038                    CurrentTime,
2039                    SendPositionSelection,
2040                    NULL/* lose_ownership_proc */ ,
2041                    NULL/* transfer_done_proc */);
2042 #endif
2043 }
2044
2045 #ifdef TODO_GTK
2046 /* function called when the data to Paste is ready */
2047 static void
2048 PastePositionCB (Widget w, XtPointer client_data, Atom *selection,
2049                  Atom *type, XtPointer value, unsigned long *len, int *format)
2050 {
2051   char *fenstr=value;
2052   if (value==NULL || *len==0) return; /* nothing had been selected to copy */
2053   fenstr[*len]='\0'; /* normally this string is terminated, but be safe */
2054   EditPositionPasteFEN(fenstr);
2055   XtFree(value);
2056 }
2057 #endif
2058
2059 /* called when Paste Position button is pressed,
2060  * all parameters will be NULL */
2061 void
2062 PastePositionProc ()
2063 {
2064 #ifdef TODO_GTK
2065     XtGetSelectionValue(menuBarWidget,
2066       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
2067       /* (XtSelectionCallbackProc) */ PastePositionCB,
2068       NULL, /* client_data passed to PastePositionCB */
2069
2070       /* better to use the time field from the event that triggered the
2071        * call to this function, but that isn't trivial to get
2072        */
2073       CurrentTime
2074     );
2075     return;
2076 #endif
2077 }
2078
2079 #ifdef TODO_GTK
2080 /* note: when called from menu all parameters are NULL, so no clue what the
2081  * Widget which was clicked on was, or what the click event was
2082  */
2083 /* function called when the data to Paste is ready */
2084 static void
2085 PasteGameCB (Widget w, XtPointer client_data, Atom *selection,
2086              Atom *type, XtPointer value, unsigned long *len, int *format)
2087 {
2088   FILE* f;
2089   if (value == NULL || *len == 0) {
2090     return; /* nothing had been selected to copy */
2091   }
2092   f = fopen(gamePasteFilename, "w");
2093   if (f == NULL) {
2094     DisplayError(_("Can't open temp file"), errno);
2095     return;
2096   }
2097   fwrite(value, 1, *len, f);
2098   fclose(f);
2099   XtFree(value);
2100   LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
2101 }
2102 #endif
2103
2104 /* called when Paste Game button is pressed,
2105  * all parameters will be NULL */
2106 void
2107 PasteGameProc ()
2108 {
2109 #ifdef TODO_GTK
2110     XtGetSelectionValue(menuBarWidget,
2111       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
2112       /* (XtSelectionCallbackProc) */ PasteGameCB,
2113       NULL, /* client_data passed to PasteGameCB */
2114
2115       /* better to use the time field from the event that triggered the
2116        * call to this function, but that isn't trivial to get
2117        */
2118       CurrentTime
2119     );
2120     return;
2121 #endif
2122 }
2123
2124
2125 #ifdef TODO_GTK
2126 void
2127 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2128 {
2129     QuitProc();
2130 }
2131 #endif
2132
2133 int
2134 ShiftKeys ()
2135 {   // bassic primitive for determining if modifier keys are pressed
2136     int i,j,  k=0;
2137 #ifdef TODO_GTK
2138     long int codes[] = { XK_Meta_L, XK_Meta_R, XK_Control_L, XK_Control_R, XK_Shift_L, XK_Shift_R };
2139     char keys[32];
2140     XQueryKeymap(xDisplay,keys);
2141     for(i=0; i<6; i++) {
2142         k <<= 1;
2143         j = XKeysymToKeycode(xDisplay, codes[i]);
2144         k += ( (keys[j>>3]&1<<(j&7)) != 0 );
2145     }
2146 #endif
2147     return k;
2148 }
2149
2150 #ifdef TODO_GTK
2151 static void
2152 MoveTypeInProc (Widget widget, caddr_t unused, XEvent *event)
2153 {
2154     char buf[10];
2155     KeySym sym;
2156     int n = XLookupString(&(event->xkey), buf, 10, &sym, NULL);
2157     if ( n == 1 && *buf >= 32 // printable
2158          && !(ShiftKeys() & 0x3C) // no Alt, Ctrl
2159         ) BoxAutoPopUp (buf);
2160 }
2161 #endif
2162
2163 #ifdef TODO_GTK
2164 static void
2165 UpKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2166 {   // [HGM] input: let up-arrow recall previous line from history
2167     IcsKey(1);
2168 }
2169
2170 static void
2171 DownKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2172 {   // [HGM] input: let down-arrow recall next line from history
2173     IcsKey(-1);
2174 }
2175
2176 static void
2177 EnterKeyProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2178 {
2179     IcsKey(0);
2180 }
2181
2182
2183 void
2184 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2185 {
2186         if (!TempBackwardActive) {
2187                 TempBackwardActive = True;
2188                 BackwardEvent();
2189         }
2190 }
2191
2192 void
2193 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2194 {
2195         /* Check to see if triggered by a key release event for a repeating key.
2196          * If so the next queued event will be a key press of the same key at the same time */
2197         if (XEventsQueued(xDisplay, QueuedAfterReading)) {
2198                 XEvent next;
2199                 XPeekEvent(xDisplay, &next);
2200                 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
2201                         next.xkey.keycode == event->xkey.keycode)
2202                                 return;
2203         }
2204     ForwardEvent();
2205         TempBackwardActive = False;
2206 }
2207
2208 void
2209 ManInner (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2210 {   // called as key binding
2211     char buf[MSG_SIZ];
2212     String name;
2213     if (nprms && *nprms > 0)
2214       name = prms[0];
2215     else
2216       name = "xboard";
2217     snprintf(buf, sizeof(buf), "xterm -e man %s &", name);
2218     system(buf);
2219 }
2220 #endif
2221
2222 void
2223 ManProc ()
2224 {   // called from menu
2225 #ifdef TODO_GTK
2226     ManInner(NULL, NULL, NULL, NULL);
2227 #endif
2228 }
2229
2230 void
2231 SetWindowTitle (char *text, char *title, char *icon)
2232 {
2233 #ifdef TODO_GTK
2234     Arg args[16];
2235     int i;
2236     if (appData.titleInWindow) {
2237         i = 0;
2238         XtSetArg(args[i], XtNlabel, text);   i++;
2239         XtSetValues(titleWidget, args, i);
2240     }
2241     i = 0;
2242     XtSetArg(args[i], XtNiconName, (XtArgVal) icon);    i++;
2243     XtSetArg(args[i], XtNtitle, (XtArgVal) title);      i++;
2244     XtSetValues(shellWidget, args, i);
2245     XSync(xDisplay, False);
2246 #endif
2247     if (appData.titleInWindow) {
2248         SetWidgetLabel(titleWidget, text);
2249     }
2250     gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
2251 }
2252
2253
2254 static int
2255 NullXErrorCheck (Display *dpy, XErrorEvent *error_event)
2256 {
2257     return 0;
2258 }
2259
2260 void
2261 DisplayIcsInteractionTitle (String message)
2262 {
2263 #ifdef TODO_GTK
2264   if (oldICSInteractionTitle == NULL) {
2265     /* Magic to find the old window title, adapted from vim */
2266     char *wina = getenv("WINDOWID");
2267     if (wina != NULL) {
2268       Window win = (Window) atoi(wina);
2269       Window root, parent, *children;
2270       unsigned int nchildren;
2271       int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
2272       for (;;) {
2273         if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
2274         if (!XQueryTree(xDisplay, win, &root, &parent,
2275                         &children, &nchildren)) break;
2276         if (children) XFree((void *)children);
2277         if (parent == root || parent == 0) break;
2278         win = parent;
2279       }
2280       XSetErrorHandler(oldHandler);
2281     }
2282     if (oldICSInteractionTitle == NULL) {
2283       oldICSInteractionTitle = "xterm";
2284     }
2285   }
2286   printf("\033]0;%s\007", message);
2287   fflush(stdout);
2288 #endif
2289 }
2290
2291
2292 void
2293 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
2294 {
2295     GtkWidget *w = (GtkWidget *) opt->handle;
2296     char *markup;
2297     char bgcolor[10];
2298     char fgcolor[10];
2299
2300     if (highlight) {
2301         strcpy(bgcolor, "black");
2302         strcpy(fgcolor, "white");
2303     } else {
2304         strcpy(bgcolor, "white");
2305         strcpy(fgcolor, "black");
2306     }
2307     if (timer > 0 &&
2308         appData.lowTimeWarning &&
2309         (timer / 1000) < appData.icsAlarmTime) {
2310         strcpy(fgcolor, appData.lowTimeWarningColor);
2311     }
2312
2313     if (appData.clockMode) {
2314         markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
2315                                          bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2316     } else {
2317         markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s  </span>",
2318                                          bgcolor, fgcolor, color);
2319     }
2320     gtk_label_set_markup(GTK_LABEL(w), markup);
2321     g_free(markup);
2322 }
2323
2324 #ifdef TODO_GTK
2325 static Pixmap *clockIcons[] = { &wIconPixmap, &bIconPixmap };
2326 #endif
2327
2328 void
2329 SetClockIcon (int color)
2330 {
2331 #ifdef TODO_GTK
2332     Arg args[16];
2333     Pixmap pm = *clockIcons[color];
2334     if (iconPixmap != pm) {
2335         iconPixmap = pm;
2336         XtSetArg(args[0], XtNiconPixmap, iconPixmap);
2337         XtSetValues(shellWidget, args, 1);
2338     }
2339 #endif
2340 }
2341
2342 #define INPUT_SOURCE_BUF_SIZE 8192
2343
2344 typedef struct {
2345     CPKind kind;
2346     int fd;
2347     int lineByLine;
2348     char *unused;
2349     InputCallback func;
2350     guint sid;
2351     char buf[INPUT_SOURCE_BUF_SIZE];
2352     VOIDSTAR closure;
2353 } InputSource;
2354
2355 gboolean
2356 DoInputCallback(io, cond, data)
2357      GIOChannel  *io;
2358      GIOCondition cond;
2359      gpointer    *data;
2360 {
2361   /* read input from one of the input source (for example a chess program, ICS, etc).
2362    * and call a function that will handle the input
2363    */
2364
2365     int count;
2366     int error;
2367     char *p, *q;
2368
2369     /* All information (callback function, file descriptor, etc) is
2370      * saved in an InputSource structure
2371      */
2372     InputSource *is = (InputSource *) data;
2373
2374     if (is->lineByLine) {
2375         count = read(is->fd, is->unused,
2376                      INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
2377         if (count <= 0) {
2378             (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
2379             return True;
2380         }
2381         is->unused += count;
2382         p = is->buf;
2383         /* break input into lines and call the callback function on each
2384          * line
2385          */
2386         while (p < is->unused) {
2387             q = memchr(p, '\n', is->unused - p);
2388             if (q == NULL) break;
2389             q++;
2390             (is->func)(is, is->closure, p, q - p, 0);
2391             p = q;
2392         }
2393         /* remember not yet used part of the buffer */
2394         q = is->buf;
2395         while (p < is->unused) {
2396             *q++ = *p++;
2397         }
2398         is->unused = q;
2399     } else {
2400       /* read maximum length of input buffer and send the whole buffer
2401        * to the callback function
2402        */
2403         count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2404         if (count == -1)
2405           error = errno;
2406         else
2407           error = 0;
2408         (is->func)(is, is->closure, is->buf, count, error);
2409     }
2410     return True; // Must return true or the watch will be removed
2411 }
2412
2413 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2414      ProcRef pr;
2415      int lineByLine;
2416      InputCallback func;
2417      VOIDSTAR closure;
2418 {
2419     InputSource *is;
2420     GIOChannel *channel;
2421     ChildProc *cp = (ChildProc *) pr;
2422
2423     is = (InputSource *) calloc(1, sizeof(InputSource));
2424     is->lineByLine = lineByLine;
2425     is->func = func;
2426     if (pr == NoProc) {
2427         is->kind = CPReal;
2428         is->fd = fileno(stdin);
2429     } else {
2430         is->kind = cp->kind;
2431         is->fd = cp->fdFrom;
2432     }
2433     if (lineByLine)
2434       is->unused = is->buf;
2435     else
2436       is->unused = NULL;
2437
2438    /* GTK-TODO: will this work on windows?*/
2439
2440     channel = g_io_channel_unix_new(is->fd);
2441     g_io_channel_set_close_on_unref (channel, TRUE);
2442     is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2443
2444     is->closure = closure;
2445     return (InputSourceRef) is;
2446 }
2447
2448
2449 void
2450 RemoveInputSource(isr)
2451      InputSourceRef isr;
2452 {
2453     InputSource *is = (InputSource *) isr;
2454
2455     if (is->sid == 0) return;
2456     g_source_remove(is->sid);
2457     is->sid = 0;
2458     return;
2459 }
2460
2461 #ifndef HAVE_USLEEP
2462
2463 static Boolean frameWaiting;
2464
2465 static RETSIGTYPE
2466 FrameAlarm (int sig)
2467 {
2468   frameWaiting = False;
2469   /* In case System-V style signals.  Needed?? */
2470   signal(SIGALRM, FrameAlarm);
2471 }
2472
2473 void
2474 FrameDelay (int time)
2475 {
2476   struct itimerval delay;
2477
2478   if (time > 0) {
2479     frameWaiting = True;
2480     signal(SIGALRM, FrameAlarm);
2481     delay.it_interval.tv_sec =
2482       delay.it_value.tv_sec = time / 1000;
2483     delay.it_interval.tv_usec =
2484       delay.it_value.tv_usec = (time % 1000) * 1000;
2485     setitimer(ITIMER_REAL, &delay, NULL);
2486     while (frameWaiting) pause();
2487     delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2488     delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2489     setitimer(ITIMER_REAL, &delay, NULL);
2490   }
2491 }
2492
2493 #else
2494
2495 void
2496 FrameDelay (int time)
2497 {
2498 #ifdef TODO_GTK
2499   XSync(xDisplay, False);
2500 #endif
2501   if (time > 0)
2502     usleep(time * 1000);
2503 }
2504
2505 #endif
2506
2507 static void
2508 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2509 {
2510     char buf[MSG_SIZ], *logoName = buf;
2511     if(appData.logo[n][0]) {
2512         logoName = appData.logo[n];
2513     } else if(appData.autoLogo) {
2514         if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2515             sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2516         } else if(appData.directory[n] && appData.directory[n][0]) {
2517             sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2518         }
2519     }
2520     if(logoName[0])
2521         { ASSIGN(cps->programLogo, logoName); }
2522 }
2523
2524 void
2525 UpdateLogos (int displ)
2526 {
2527     if(optList[W_WHITE-1].handle == NULL) return;
2528     LoadLogo(&first, 0, 0);
2529     LoadLogo(&second, 1, appData.icsActive);
2530     if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2531     return;
2532 }
2533