3157544677f5488c89e1a2805469650881b39db3
[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     ParseIcsTextColors();
1246
1247 #ifdef TODO_GTK
1248     XtAppAddActions(appContext, boardActions, XtNumber(boardActions));
1249 #endif
1250
1251     /*
1252      * widget hierarchy
1253      */
1254     if (tinyLayout) {
1255         layoutName = "tinyLayout";
1256     } else if (smallLayout) {
1257         layoutName = "smallLayout";
1258     } else {
1259         layoutName = "normalLayout";
1260     }
1261
1262     optList = BoardPopUp(squareSize, lineGap, (void*)
1263 #ifdef TODO_GTK
1264 #if ENABLE_NLS
1265                                                 &clockFontSet);
1266 #else
1267                                                 clockFontStruct);
1268 #endif
1269 #else
1270 0);
1271 #endif
1272     InitDrawingHandle(optList + W_BOARD);
1273     currBoard        = &optList[W_BOARD];
1274     boardWidget      = optList[W_BOARD].handle;
1275     menuBarWidget    = optList[W_MENU].handle;
1276     dropMenu         = optList[W_DROP].handle;
1277     titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1278 #ifdef TODO_GTK
1279     formWidget  = XtParent(boardWidget);
1280     XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1281     XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1282     XtGetValues(optList[W_WHITE].handle, args, 2);
1283     if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1284       XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1285       XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1286       XtGetValues(optList[W_PAUSE].handle, args, 2);
1287     }
1288 #endif
1289
1290 #ifdef TODO_GTK
1291     xBoardWindow = XtWindow(boardWidget);
1292 #endif
1293
1294     // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1295     //       not need to go into InitDrawingSizes().
1296
1297     InitMenuMarkers();
1298
1299     /*
1300      * Create an icon.
1301      */
1302 #ifdef TODO_GTK
1303     ReadBitmap(&wIconPixmap, "icon_white.bm",
1304                icon_white_bits, icon_white_width, icon_white_height);
1305     ReadBitmap(&bIconPixmap, "icon_black.bm",
1306                icon_black_bits, icon_black_width, icon_black_height);
1307     iconPixmap = wIconPixmap;
1308     i = 0;
1309     XtSetArg(args[i], XtNiconPixmap, iconPixmap);  i++;
1310     XtSetValues(shellWidget, args, i);
1311 #endif
1312
1313     /*
1314      * Create a cursor for the board widget.
1315      */
1316 #ifdef TODO_GTK
1317     window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1318     XChangeWindowAttributes(xDisplay, xBoardWindow,
1319                             CWCursor, &window_attributes);
1320 #endif
1321
1322     /*
1323      * Inhibit shell resizing.
1324      */
1325 #ifdef TODO_GTK
1326     shellArgs[0].value = (XtArgVal) &w;
1327     shellArgs[1].value = (XtArgVal) &h;
1328     XtGetValues(shellWidget, shellArgs, 2);
1329     shellArgs[4].value = shellArgs[2].value = w;
1330     shellArgs[5].value = shellArgs[3].value = h;
1331 //    XtSetValues(shellWidget, &shellArgs[2], 4);
1332 #endif
1333     marginW =  w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1334     marginH =  h - boardHeight;
1335
1336 #ifdef TODO_GTK
1337     CatchDeleteWindow(shellWidget, "QuitProc");
1338 #endif
1339
1340     CreateAnyPieces();
1341     CreateGrid();
1342
1343     if(appData.logoSize)
1344     {   // locate and read user logo
1345         char buf[MSG_SIZ];
1346         snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1347         ASSIGN(userLogo, buf);
1348     }
1349
1350     if (appData.animate || appData.animateDragging)
1351       CreateAnimVars();
1352
1353 #ifdef TODO_GTK
1354     XtAugmentTranslations(formWidget,
1355                           XtParseTranslationTable(globalTranslations));
1356
1357     XtAddEventHandler(formWidget, KeyPressMask, False,
1358                       (XtEventHandler) MoveTypeInProc, NULL);
1359     XtAddEventHandler(shellWidget, StructureNotifyMask, False,
1360                       (XtEventHandler) EventProc, NULL);
1361 #endif
1362
1363     /* [AS] Restore layout */
1364     if( wpMoveHistory.visible ) {
1365       HistoryPopUp();
1366     }
1367
1368     if( wpEvalGraph.visible )
1369       {
1370         EvalGraphPopUp();
1371       };
1372
1373     if( wpEngineOutput.visible ) {
1374       EngineOutputPopUp();
1375     }
1376
1377     InitBackEnd2();
1378
1379     if (errorExitStatus == -1) {
1380         if (appData.icsActive) {
1381             /* We now wait until we see "login:" from the ICS before
1382                sending the logon script (problems with timestamp otherwise) */
1383             /*ICSInitScript();*/
1384             if (appData.icsInputBox) ICSInputBoxPopUp();
1385         }
1386
1387     #ifdef SIGWINCH
1388     signal(SIGWINCH, TermSizeSigHandler);
1389     #endif
1390         signal(SIGINT, IntSigHandler);
1391         signal(SIGTERM, IntSigHandler);
1392         if (*appData.cmailGameName != NULLCHAR) {
1393             signal(SIGUSR1, CmailSigHandler);
1394         }
1395     }
1396
1397     gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1398     InitPosition(TRUE);
1399     UpdateLogos(TRUE);
1400 //    XtSetKeyboardFocus(shellWidget, formWidget);
1401 #ifdef TODO_GTK
1402     XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1403 #endif
1404
1405     /* check for GTK events and process them */
1406 //    gtk_main();
1407 while(1) {
1408 gtk_main_iteration();
1409 }
1410
1411     if (appData.debugMode) fclose(debugFP); // [DM] debug
1412     return 0;
1413 }
1414
1415 RETSIGTYPE
1416 TermSizeSigHandler (int sig)
1417 {
1418     update_ics_width();
1419 }
1420
1421 RETSIGTYPE
1422 IntSigHandler (int sig)
1423 {
1424     ExitEvent(sig);
1425 }
1426
1427 RETSIGTYPE
1428 CmailSigHandler (int sig)
1429 {
1430     int dummy = 0;
1431     int error;
1432
1433     signal(SIGUSR1, SIG_IGN);   /* suspend handler     */
1434
1435     /* Activate call-back function CmailSigHandlerCallBack()             */
1436     OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1437
1438     signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1439 }
1440
1441 void
1442 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1443 {
1444     BoardToTop();
1445     ReloadCmailMsgEvent(TRUE);  /* Reload cmail msg  */
1446 }
1447 /**** end signal code ****/
1448
1449
1450 #define Abs(n) ((n)<0 ? -(n) : (n))
1451
1452 #ifdef ENABLE_NLS
1453 char *
1454 InsertPxlSize (char *pattern, int targetPxlSize)
1455 {
1456     char *base_fnt_lst, strInt[12], *p, *q;
1457     int alternatives, i, len, strIntLen;
1458
1459     /*
1460      * Replace the "*" (if present) in the pixel-size slot of each
1461      * alternative with the targetPxlSize.
1462      */
1463     p = pattern;
1464     alternatives = 1;
1465     while ((p = strchr(p, ',')) != NULL) {
1466       alternatives++;
1467       p++;
1468     }
1469     snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1470     strIntLen = strlen(strInt);
1471     base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1472
1473     p = pattern;
1474     q = base_fnt_lst;
1475     while (alternatives--) {
1476       char *comma = strchr(p, ',');
1477       for (i=0; i<14; i++) {
1478         char *hyphen = strchr(p, '-');
1479         if (!hyphen) break;
1480         if (comma && hyphen > comma) break;
1481         len = hyphen + 1 - p;
1482         if (i == 7 && *p == '*' && len == 2) {
1483           p += len;
1484           memcpy(q, strInt, strIntLen);
1485           q += strIntLen;
1486           *q++ = '-';
1487         } else {
1488           memcpy(q, p, len);
1489           p += len;
1490           q += len;
1491         }
1492       }
1493       if (!comma) break;
1494       len = comma + 1 - p;
1495       memcpy(q, p, len);
1496       p += len;
1497       q += len;
1498     }
1499     strcpy(q, p);
1500
1501     return base_fnt_lst;
1502 }
1503
1504 #ifdef TODO_GTK
1505 XFontSet
1506 CreateFontSet (char *base_fnt_lst)
1507 {
1508     XFontSet fntSet;
1509     char **missing_list;
1510     int missing_count;
1511     char *def_string;
1512
1513     fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1514                             &missing_list, &missing_count, &def_string);
1515     if (appData.debugMode) {
1516       int i, count;
1517       XFontStruct **font_struct_list;
1518       char **font_name_list;
1519       fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1520       if (fntSet) {
1521         fprintf(debugFP, " got list %s, locale %s\n",
1522                 XBaseFontNameListOfFontSet(fntSet),
1523                 XLocaleOfFontSet(fntSet));
1524         count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1525         for (i = 0; i < count; i++) {
1526           fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1527         }
1528       }
1529       for (i = 0; i < missing_count; i++) {
1530         fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1531       }
1532     }
1533     if (fntSet == NULL) {
1534       fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1535       exit(2);
1536     }
1537     return fntSet;
1538 }
1539 #endif
1540 #else // not ENABLE_NLS
1541 /*
1542  * Find a font that matches "pattern" that is as close as
1543  * possible to the targetPxlSize.  Prefer fonts that are k
1544  * pixels smaller to fonts that are k pixels larger.  The
1545  * pattern must be in the X Consortium standard format,
1546  * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1547  * The return value should be freed with XtFree when no
1548  * longer needed.
1549  */
1550 char *
1551 FindFont (char *pattern, int targetPxlSize)
1552 {
1553     char **fonts, *p, *best, *scalable, *scalableTail;
1554     int i, j, nfonts, minerr, err, pxlSize;
1555
1556 #ifdef TODO_GTK
1557     fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1558     if (nfonts < 1) {
1559         fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1560                 programName, pattern);
1561         exit(2);
1562     }
1563
1564     best = fonts[0];
1565     scalable = NULL;
1566     minerr = 999999;
1567     for (i=0; i<nfonts; i++) {
1568         j = 0;
1569         p = fonts[i];
1570         if (*p != '-') continue;
1571         while (j < 7) {
1572             if (*p == NULLCHAR) break;
1573             if (*p++ == '-') j++;
1574         }
1575         if (j < 7) continue;
1576         pxlSize = atoi(p);
1577         if (pxlSize == 0) {
1578             scalable = fonts[i];
1579             scalableTail = p;
1580         } else {
1581             err = pxlSize - targetPxlSize;
1582             if (Abs(err) < Abs(minerr) ||
1583                 (minerr > 0 && err < 0 && -err == minerr)) {
1584                 best = fonts[i];
1585                 minerr = err;
1586             }
1587         }
1588     }
1589     if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1590         /* If the error is too big and there is a scalable font,
1591            use the scalable font. */
1592         int headlen = scalableTail - scalable;
1593         p = (char *) XtMalloc(strlen(scalable) + 10);
1594         while (isdigit(*scalableTail)) scalableTail++;
1595         sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1596     } else {
1597         p = (char *) XtMalloc(strlen(best) + 2);
1598         safeStrCpy(p, best, strlen(best)+1 );
1599     }
1600     if (appData.debugMode) {
1601         fprintf(debugFP, _("resolved %s at pixel size %d\n  to %s\n"),
1602                 pattern, targetPxlSize, p);
1603     }
1604     XFreeFontNames(fonts);
1605 #endif
1606     return p;
1607 }
1608 #endif
1609
1610 void
1611 EnableNamedMenuItem (char *menuRef, int state)
1612 {
1613     MenuItem *item = MenuNameToItem(menuRef);
1614
1615     if(item) gtk_widget_set_sensitive(item->handle, state);
1616 }
1617
1618 void
1619 EnableButtonBar (int state)
1620 {
1621 #ifdef TODO_GTK
1622     XtSetSensitive(optList[W_BUTTON].handle, state);
1623 #endif
1624 }
1625
1626
1627 void
1628 SetMenuEnables (Enables *enab)
1629 {
1630   while (enab->name != NULL) {
1631     EnableNamedMenuItem(enab->name, enab->value);
1632     enab++;
1633   }
1634 }
1635
1636 #ifdef TODO_GTK
1637 gboolean KeyPressProc(window, eventkey, data)
1638      GtkWindow *window;
1639      GdkEventKey  *eventkey;
1640      gpointer data;
1641 {
1642
1643     MoveTypeInProc(eventkey); // pop up for typed in moves
1644
1645     // handle shift+<number> cases
1646     if (eventkey->state & GDK_SHIFT_MASK) {
1647         guint keyval;
1648
1649         gdk_keymap_translate_keyboard_state(NULL, eventkey->hardware_keycode,
1650                                             0, eventkey->group,
1651                                             &keyval, NULL, NULL, NULL);
1652         switch(keyval) {
1653             case GDK_1:
1654                 AskQuestionEvent("Direct command", "Send to chess program:", "", "1");
1655                 break;
1656             case GDK_2:
1657                 AskQuestionEvent("Direct command", "Send to second chess program:", "", "2");
1658                 break;
1659             default:
1660                 break;
1661         }
1662     }
1663
1664     /* check for other key values */
1665     switch(eventkey->keyval) {
1666         case GDK_question:
1667           AboutGameEvent();
1668           break;
1669         default:
1670           break;
1671     }
1672     return False;
1673 }
1674 void
1675 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1676 {   // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1677     MenuItem *item;
1678     if(*nprms == 0) return;
1679     item = MenuNameToItem(prms[0]);
1680     if(item) ((MenuProc *) item->proc) ();
1681 }
1682 #endif
1683
1684 void
1685 SetupDropMenu ()
1686 {
1687 #ifdef TODO_GTK
1688     int i, j, count;
1689     char label[32];
1690     Arg args[16];
1691     Widget entry;
1692     char* p;
1693
1694     for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1695         entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1696         p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1697                    dmEnables[i].piece);
1698         XtSetSensitive(entry, p != NULL || !appData.testLegality
1699                        /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1700                                        && !appData.icsActive));
1701         count = 0;
1702         while (p && *p++ == dmEnables[i].piece) count++;
1703         snprintf(label, sizeof(label), "%s  %d", dmEnables[i].widget, count);
1704         j = 0;
1705         XtSetArg(args[j], XtNlabel, label); j++;
1706         XtSetValues(entry, args, j);
1707     }
1708 #endif
1709 }
1710
1711 static void
1712 do_flash_delay (unsigned long msec)
1713 {
1714     TimeDelay(msec);
1715 }
1716
1717 void
1718 FlashDelay (int flash_delay)
1719 {
1720 #ifdef TODO_GTK
1721         XSync(xDisplay, False);
1722         if(flash_delay) do_flash_delay(flash_delay);
1723 #endif
1724 }
1725
1726 double
1727 Fraction (int x, int start, int stop)
1728 {
1729    double f = ((double) x - start)/(stop - start);
1730    if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1731    return f;
1732 }
1733
1734 static WindowPlacement wpNew;
1735
1736 #ifdef TODO_GTK
1737 void
1738 CoDrag (Widget sh, WindowPlacement *wp)
1739 {
1740     Arg args[16];
1741     int j=0, touch=0, fudge = 2;
1742     GetActualPlacement(sh, wp);
1743     if(abs(wpMain.x + wpMain.width + 2*frameX - wp->x)         < fudge) touch = 1; else // right touch
1744     if(abs(wp->x + wp->width + 2*frameX - wpMain.x)            < fudge) touch = 2; else // left touch
1745     if(abs(wpMain.y + wpMain.height + frameX + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1746     if(abs(wp->y + wp->height + frameX + frameY - wpMain.y)    < fudge) touch = 4;      // top touch
1747     if(!touch ) return; // only windows that touch co-move
1748     if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1749         int heightInc = wpNew.height - wpMain.height;
1750         double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1751         double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1752         wp->y += fracTop * heightInc;
1753         heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1754         if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1755     } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1756         int widthInc = wpNew.width - wpMain.width;
1757         double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1758         double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1759         wp->y += fracLeft * widthInc;
1760         widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1761         if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1762     }
1763     wp->x += wpNew.x - wpMain.x;
1764     wp->y += wpNew.y - wpMain.y;
1765     if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1766     if(touch == 3) wp->y += wpNew.height - wpMain.height;
1767 #ifdef TODO_GTK
1768     XtSetArg(args[j], XtNx, wp->x); j++;
1769     XtSetArg(args[j], XtNy, wp->y); j++;
1770     XtSetValues(sh, args, j);
1771 #endif
1772 }
1773
1774 void
1775 ReSize (WindowPlacement *wp)
1776 {
1777         int sqx, sqy, w, h;
1778         if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1779         sqx = (wp->width  - lineGap - marginW) / BOARD_WIDTH - lineGap;
1780         sqy = (wp->height - lineGap - marginH) / BOARD_HEIGHT - lineGap;
1781         if(sqy < sqx) sqx = sqy;
1782         if(sqx != squareSize) {
1783             squareSize = sqx; // adopt new square size
1784             CreatePNGPieces(); // make newly scaled pieces
1785             InitDrawingSizes(0, 0); // creates grid etc.
1786         } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1787         w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1788         h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1789         if(optList[W_BOARD].max   > w) optList[W_BOARD].max = w;
1790         if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1791 }
1792
1793 #ifdef TODO_GTK
1794 static XtIntervalId delayedDragID = 0;
1795 #else
1796 static int delayedDragID = 0;
1797 #endif
1798
1799 void
1800 DragProc ()
1801 {
1802         static int busy;
1803         if(busy) return;
1804
1805         busy = 1;
1806         GetActualPlacement(shellWidget, &wpNew);
1807         if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1808            wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1809             busy = 0; return; // false alarm
1810         }
1811         ReSize(&wpNew);
1812         if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1813         if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1814         if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1815         if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1816         wpMain = wpNew;
1817         DrawPosition(True, NULL);
1818         delayedDragID = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1819         busy = 0;
1820 }
1821 #endif
1822
1823 void
1824 DelayedDrag ()
1825 {
1826 #ifdef TODO_GTK
1827     if(delayedDragID) XtRemoveTimeOut(delayedDragID); // cancel pending
1828     delayedDragID =
1829       XtAppAddTimeOut(appContext, 200, (XtTimerCallbackProc) DragProc, (XtPointer) 0); // and schedule new one 50 msec later
1830 #endif
1831 }
1832
1833 #ifdef TODO_GTK
1834 void
1835 EventProc (Widget widget, caddr_t unused, XEvent *event)
1836 {
1837     if(XtIsRealized(widget) && event->type == ConfigureNotify || appData.useStickyWindows)
1838         DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1839 }
1840 #endif
1841
1842 /*
1843  * event handler for redrawing the board
1844  */
1845 #ifdef TODO_GTK
1846 void
1847 DrawPositionProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1848 {
1849     DrawPosition(True, NULL);
1850 }
1851 #endif
1852
1853
1854 #ifdef TODO_GTK
1855 void
1856 HandlePV (Widget w, XEvent * event, String * params, Cardinal * nParams)
1857 {   // [HGM] pv: walk PV
1858     MovePV(event->xmotion.x, event->xmotion.y, lineGap + BOARD_HEIGHT * (squareSize + lineGap));
1859 }
1860 #endif
1861
1862 static int savedIndex;  /* gross that this is global */
1863
1864 #ifdef TODO_GTK
1865 void
1866 CommentClick (Widget w, XEvent * event, String * params, Cardinal * nParams)
1867 {
1868         String val;
1869         XawTextPosition index, dummy;
1870         Arg arg;
1871
1872         XawTextGetSelectionPos(w, &index, &dummy);
1873         XtSetArg(arg, XtNstring, &val);
1874         XtGetValues(w, &arg, 1);
1875         ReplaceComment(savedIndex, val);
1876         if(savedIndex != currentMove) ToNrEvent(savedIndex);
1877         LoadVariation( index, val ); // [HGM] also does the actual moving to it, now
1878 }
1879 #endif
1880
1881 void
1882 EditCommentPopUp (int index, char *title, char *text)
1883 {
1884     savedIndex = index;
1885     if (text == NULL) text = "";
1886     NewCommentPopup(title, text, index);
1887 }
1888
1889 void
1890 CommentPopUp (char *title, char *text)
1891 {
1892     savedIndex = currentMove; // [HGM] vari
1893     NewCommentPopup(title, text, currentMove);
1894 }
1895
1896 void
1897 CommentPopDown ()
1898 {
1899     PopDown(CommentDlg);
1900 }
1901
1902
1903 /* Disable all user input other than deleting the window */
1904 static int frozen = 0;
1905
1906 void
1907 FreezeUI ()
1908 {
1909   if (frozen) return;
1910   /* Grab by a widget that doesn't accept input */
1911   gtk_grab_add(optList[W_MESSG].handle);
1912   frozen = 1;
1913 }
1914
1915 /* Undo a FreezeUI */
1916 void
1917 ThawUI ()
1918 {
1919   if (!frozen) return;
1920   gtk_grab_remove(optList[W_MESSG].handle);
1921   frozen = 0;
1922 }
1923
1924 void
1925 ModeHighlight ()
1926 {
1927     static int oldPausing = FALSE;
1928     static GameMode oldmode = (GameMode) -1;
1929     char *wname;
1930     if (!boardWidget) return;
1931
1932     if (pausing != oldPausing) {
1933         oldPausing = pausing;
1934         MarkMenuItem("Mode.Pause", pausing);
1935
1936         if (appData.showButtonBar) {
1937           /* Always toggle, don't set.  Previous code messes up when
1938              invoked while the button is pressed, as releasing it
1939              toggles the state again. */
1940             GdkColor color;     
1941             gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1942             gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1943         }
1944     }
1945
1946     wname = ModeToWidgetName(oldmode);
1947     if (wname != NULL) {
1948         MarkMenuItem(wname, False);
1949     }
1950     wname = ModeToWidgetName(gameMode);
1951     if (wname != NULL) {
1952         MarkMenuItem(wname, True);
1953     }
1954     oldmode = gameMode;
1955     MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1956
1957     /* Maybe all the enables should be handled here, not just this one */
1958     EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1959
1960     DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1961 }
1962
1963
1964 /*
1965  * Button/menu procedures
1966  */
1967
1968 #ifdef TODO_GTK
1969 /* this variable is shared between CopyPositionProc and SendPositionSelection */
1970 char *selected_fen_position=NULL;
1971
1972 Boolean
1973 SendPositionSelection (Widget w, Atom *selection, Atom *target,
1974                        Atom *type_return, XtPointer *value_return,
1975                        unsigned long *length_return, int *format_return)
1976 {
1977   char *selection_tmp;
1978
1979 //  if (!selected_fen_position) return False; /* should never happen */
1980   if (*target == XA_STRING || *target == XA_UTF8_STRING(xDisplay)){
1981    if (!selected_fen_position) { // since it never happens, we use it for indicating a game is being sent
1982     FILE* f = fopen(gameCopyFilename, "r"); // This code, taken from SendGameSelection, now merges the two
1983     long len;
1984     size_t count;
1985     if (f == NULL) return False;
1986     fseek(f, 0, 2);
1987     len = ftell(f);
1988     rewind(f);
1989     selection_tmp = XtMalloc(len + 1);
1990     count = fread(selection_tmp, 1, len, f);
1991     fclose(f);
1992     if (len != count) {
1993       XtFree(selection_tmp);
1994       return False;
1995     }
1996     selection_tmp[len] = NULLCHAR;
1997    } else {
1998     /* note: since no XtSelectionDoneProc was registered, Xt will
1999      * automatically call XtFree on the value returned.  So have to
2000      * make a copy of it allocated with XtMalloc */
2001     selection_tmp= XtMalloc(strlen(selected_fen_position)+16);
2002     safeStrCpy(selection_tmp, selected_fen_position, strlen(selected_fen_position)+16 );
2003    }
2004
2005     *value_return=selection_tmp;
2006     *length_return=strlen(selection_tmp);
2007     *type_return=*target;
2008     *format_return = 8; /* bits per byte */
2009     return True;
2010   } else if (*target == XA_TARGETS(xDisplay)) {
2011     Atom *targets_tmp = (Atom *) XtMalloc(2 * sizeof(Atom));
2012     targets_tmp[0] = XA_UTF8_STRING(xDisplay);
2013     targets_tmp[1] = XA_STRING;
2014     *value_return = targets_tmp;
2015     *type_return = XA_ATOM;
2016     *length_return = 2;
2017 #if 0
2018     // This code leads to a read of value_return out of bounds on 64-bit systems.
2019     // Other code which I have seen always sets *format_return to 32 independent of
2020     // sizeof(Atom) without adjusting *length_return. For instance see TextConvertSelection()
2021     // at http://cgit.freedesktop.org/xorg/lib/libXaw/tree/src/Text.c -- BJ
2022     *format_return = 8 * sizeof(Atom);
2023     if (*format_return > 32) {
2024       *length_return *= *format_return / 32;
2025       *format_return = 32;
2026     }
2027 #else
2028     *format_return = 32;
2029 #endif
2030     return True;
2031   } else {
2032     return False;
2033   }
2034 }
2035 #endif
2036
2037 /* note: when called from menu all parameters are NULL, so no clue what the
2038  * Widget which was clicked on was, or what the click event was
2039  */
2040 void
2041 CopySomething (char *src)
2042 {
2043 #ifdef TODO_GTK
2044     selected_fen_position = src;
2045     /*
2046      * Set both PRIMARY (the selection) and CLIPBOARD, since we don't
2047      * have a notion of a position that is selected but not copied.
2048      * See http://www.freedesktop.org/wiki/Specifications/ClipboardsWiki
2049      */
2050     XtOwnSelection(menuBarWidget, XA_PRIMARY,
2051                    CurrentTime,
2052                    SendPositionSelection,
2053                    NULL/* lose_ownership_proc */ ,
2054                    NULL/* transfer_done_proc */);
2055     XtOwnSelection(menuBarWidget, XA_CLIPBOARD(xDisplay),
2056                    CurrentTime,
2057                    SendPositionSelection,
2058                    NULL/* lose_ownership_proc */ ,
2059                    NULL/* transfer_done_proc */);
2060 #endif
2061 }
2062
2063 #ifdef TODO_GTK
2064 /* function called when the data to Paste is ready */
2065 static void
2066 PastePositionCB (Widget w, XtPointer client_data, Atom *selection,
2067                  Atom *type, XtPointer value, unsigned long *len, int *format)
2068 {
2069   char *fenstr=value;
2070   if (value==NULL || *len==0) return; /* nothing had been selected to copy */
2071   fenstr[*len]='\0'; /* normally this string is terminated, but be safe */
2072   EditPositionPasteFEN(fenstr);
2073   XtFree(value);
2074 }
2075 #endif
2076
2077 /* called when Paste Position button is pressed,
2078  * all parameters will be NULL */
2079 void
2080 PastePositionProc ()
2081 {
2082 #ifdef TODO_GTK
2083     XtGetSelectionValue(menuBarWidget,
2084       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
2085       /* (XtSelectionCallbackProc) */ PastePositionCB,
2086       NULL, /* client_data passed to PastePositionCB */
2087
2088       /* better to use the time field from the event that triggered the
2089        * call to this function, but that isn't trivial to get
2090        */
2091       CurrentTime
2092     );
2093     return;
2094 #endif
2095 }
2096
2097 #ifdef TODO_GTK
2098 /* note: when called from menu all parameters are NULL, so no clue what the
2099  * Widget which was clicked on was, or what the click event was
2100  */
2101 /* function called when the data to Paste is ready */
2102 static void
2103 PasteGameCB (Widget w, XtPointer client_data, Atom *selection,
2104              Atom *type, XtPointer value, unsigned long *len, int *format)
2105 {
2106   FILE* f;
2107   if (value == NULL || *len == 0) {
2108     return; /* nothing had been selected to copy */
2109   }
2110   f = fopen(gamePasteFilename, "w");
2111   if (f == NULL) {
2112     DisplayError(_("Can't open temp file"), errno);
2113     return;
2114   }
2115   fwrite(value, 1, *len, f);
2116   fclose(f);
2117   XtFree(value);
2118   LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
2119 }
2120 #endif
2121
2122 /* called when Paste Game button is pressed,
2123  * all parameters will be NULL */
2124 void
2125 PasteGameProc ()
2126 {
2127 #ifdef TODO_GTK
2128     XtGetSelectionValue(menuBarWidget,
2129       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
2130       /* (XtSelectionCallbackProc) */ PasteGameCB,
2131       NULL, /* client_data passed to PasteGameCB */
2132
2133       /* better to use the time field from the event that triggered the
2134        * call to this function, but that isn't trivial to get
2135        */
2136       CurrentTime
2137     );
2138     return;
2139 #endif
2140 }
2141
2142
2143 #ifdef TODO_GTK
2144 void
2145 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2146 {
2147     QuitProc();
2148 }
2149 #endif
2150
2151 int
2152 ShiftKeys ()
2153 {   // bassic primitive for determining if modifier keys are pressed
2154     int i,j,  k=0;
2155 #ifdef TODO_GTK
2156     long int codes[] = { XK_Meta_L, XK_Meta_R, XK_Control_L, XK_Control_R, XK_Shift_L, XK_Shift_R };
2157     char keys[32];
2158     XQueryKeymap(xDisplay,keys);
2159     for(i=0; i<6; i++) {
2160         k <<= 1;
2161         j = XKeysymToKeycode(xDisplay, codes[i]);
2162         k += ( (keys[j>>3]&1<<(j&7)) != 0 );
2163     }
2164 #endif
2165     return k;
2166 }
2167
2168 void MoveTypeInProc(eventkey)
2169     GdkEventKey  *eventkey;
2170 {
2171     char buf[10];
2172
2173     // ingnore if ctrl or alt is pressed
2174     if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) {
2175         return;
2176     }
2177
2178     buf[0]=eventkey->keyval;
2179     buf[1]='\0';
2180     if (*buf >= 32)        
2181         BoxAutoPopUp (buf);
2182 }
2183
2184 #ifdef TODO_GTK
2185 void
2186 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2187 {
2188         if (!TempBackwardActive) {
2189                 TempBackwardActive = True;
2190                 BackwardEvent();
2191         }
2192 }
2193
2194 void
2195 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2196 {
2197         /* Check to see if triggered by a key release event for a repeating key.
2198          * If so the next queued event will be a key press of the same key at the same time */
2199         if (XEventsQueued(xDisplay, QueuedAfterReading)) {
2200                 XEvent next;
2201                 XPeekEvent(xDisplay, &next);
2202                 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
2203                         next.xkey.keycode == event->xkey.keycode)
2204                                 return;
2205         }
2206     ForwardEvent();
2207         TempBackwardActive = False;
2208 }
2209
2210 void
2211 ManInner (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2212 {   // called as key binding
2213     char buf[MSG_SIZ];
2214     String name;
2215     if (nprms && *nprms > 0)
2216       name = prms[0];
2217     else
2218       name = "xboard";
2219     snprintf(buf, sizeof(buf), "xterm -e man %s &", name);
2220     system(buf);
2221 }
2222 #endif
2223
2224 void
2225 ManProc ()
2226 {   // called from menu
2227 #ifdef TODO_GTK
2228     ManInner(NULL, NULL, NULL, NULL);
2229 #endif
2230 }
2231
2232 void
2233 SetWindowTitle (char *text, char *title, char *icon)
2234 {
2235 #ifdef TODO_GTK
2236     Arg args[16];
2237     int i;
2238     if (appData.titleInWindow) {
2239         i = 0;
2240         XtSetArg(args[i], XtNlabel, text);   i++;
2241         XtSetValues(titleWidget, args, i);
2242     }
2243     i = 0;
2244     XtSetArg(args[i], XtNiconName, (XtArgVal) icon);    i++;
2245     XtSetArg(args[i], XtNtitle, (XtArgVal) title);      i++;
2246     XtSetValues(shellWidget, args, i);
2247     XSync(xDisplay, False);
2248 #endif
2249     if (appData.titleInWindow) {
2250         SetWidgetLabel(titleWidget, text);
2251     }
2252     gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
2253 }
2254
2255
2256 static int
2257 NullXErrorCheck (Display *dpy, XErrorEvent *error_event)
2258 {
2259     return 0;
2260 }
2261
2262 void
2263 DisplayIcsInteractionTitle (String message)
2264 {
2265 #ifdef TODO_GTK
2266   if (oldICSInteractionTitle == NULL) {
2267     /* Magic to find the old window title, adapted from vim */
2268     char *wina = getenv("WINDOWID");
2269     if (wina != NULL) {
2270       Window win = (Window) atoi(wina);
2271       Window root, parent, *children;
2272       unsigned int nchildren;
2273       int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
2274       for (;;) {
2275         if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
2276         if (!XQueryTree(xDisplay, win, &root, &parent,
2277                         &children, &nchildren)) break;
2278         if (children) XFree((void *)children);
2279         if (parent == root || parent == 0) break;
2280         win = parent;
2281       }
2282       XSetErrorHandler(oldHandler);
2283     }
2284     if (oldICSInteractionTitle == NULL) {
2285       oldICSInteractionTitle = "xterm";
2286     }
2287   }
2288   printf("\033]0;%s\007", message);
2289   fflush(stdout);
2290 #endif
2291 }
2292
2293
2294 void
2295 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
2296 {
2297     GtkWidget *w = (GtkWidget *) opt->handle;
2298     char *markup;
2299     char bgcolor[10];
2300     char fgcolor[10];
2301
2302     if (highlight) {
2303         strcpy(bgcolor, "black");
2304         strcpy(fgcolor, "white");
2305     } else {
2306         strcpy(bgcolor, "white");
2307         strcpy(fgcolor, "black");
2308     }
2309     if (timer > 0 &&
2310         appData.lowTimeWarning &&
2311         (timer / 1000) < appData.icsAlarmTime) {
2312         strcpy(fgcolor, appData.lowTimeWarningColor);
2313     }
2314
2315     if (appData.clockMode) {
2316         markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
2317                                          bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2318     } else {
2319         markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s  </span>",
2320                                          bgcolor, fgcolor, color);
2321     }
2322     gtk_label_set_markup(GTK_LABEL(w), markup);
2323     g_free(markup);
2324 }
2325
2326 #ifdef TODO_GTK
2327 static Pixmap *clockIcons[] = { &wIconPixmap, &bIconPixmap };
2328 #endif
2329
2330 void
2331 SetClockIcon (int color)
2332 {
2333 #ifdef TODO_GTK
2334     Arg args[16];
2335     Pixmap pm = *clockIcons[color];
2336     if (iconPixmap != pm) {
2337         iconPixmap = pm;
2338         XtSetArg(args[0], XtNiconPixmap, iconPixmap);
2339         XtSetValues(shellWidget, args, 1);
2340     }
2341 #endif
2342 }
2343
2344 #define INPUT_SOURCE_BUF_SIZE 8192
2345
2346 typedef struct {
2347     CPKind kind;
2348     int fd;
2349     int lineByLine;
2350     char *unused;
2351     InputCallback func;
2352     guint sid;
2353     char buf[INPUT_SOURCE_BUF_SIZE];
2354     VOIDSTAR closure;
2355 } InputSource;
2356
2357 gboolean
2358 DoInputCallback(io, cond, data)
2359      GIOChannel  *io;
2360      GIOCondition cond;
2361      gpointer    *data;
2362 {
2363   /* read input from one of the input source (for example a chess program, ICS, etc).
2364    * and call a function that will handle the input
2365    */
2366
2367     int count;
2368     int error;
2369     char *p, *q;
2370
2371     /* All information (callback function, file descriptor, etc) is
2372      * saved in an InputSource structure
2373      */
2374     InputSource *is = (InputSource *) data;
2375
2376     if (is->lineByLine) {
2377         count = read(is->fd, is->unused,
2378                      INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
2379         if (count <= 0) {
2380             (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
2381             return True;
2382         }
2383         is->unused += count;
2384         p = is->buf;
2385         /* break input into lines and call the callback function on each
2386          * line
2387          */
2388         while (p < is->unused) {
2389             q = memchr(p, '\n', is->unused - p);
2390             if (q == NULL) break;
2391             q++;
2392             (is->func)(is, is->closure, p, q - p, 0);
2393             p = q;
2394         }
2395         /* remember not yet used part of the buffer */
2396         q = is->buf;
2397         while (p < is->unused) {
2398             *q++ = *p++;
2399         }
2400         is->unused = q;
2401     } else {
2402       /* read maximum length of input buffer and send the whole buffer
2403        * to the callback function
2404        */
2405         count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2406         if (count == -1)
2407           error = errno;
2408         else
2409           error = 0;
2410         (is->func)(is, is->closure, is->buf, count, error);
2411     }
2412     return True; // Must return true or the watch will be removed
2413 }
2414
2415 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2416      ProcRef pr;
2417      int lineByLine;
2418      InputCallback func;
2419      VOIDSTAR closure;
2420 {
2421     InputSource *is;
2422     GIOChannel *channel;
2423     ChildProc *cp = (ChildProc *) pr;
2424
2425     is = (InputSource *) calloc(1, sizeof(InputSource));
2426     is->lineByLine = lineByLine;
2427     is->func = func;
2428     if (pr == NoProc) {
2429         is->kind = CPReal;
2430         is->fd = fileno(stdin);
2431     } else {
2432         is->kind = cp->kind;
2433         is->fd = cp->fdFrom;
2434     }
2435     if (lineByLine)
2436       is->unused = is->buf;
2437     else
2438       is->unused = NULL;
2439
2440    /* GTK-TODO: will this work on windows?*/
2441
2442     channel = g_io_channel_unix_new(is->fd);
2443     g_io_channel_set_close_on_unref (channel, TRUE);
2444     is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2445
2446     is->closure = closure;
2447     return (InputSourceRef) is;
2448 }
2449
2450
2451 void
2452 RemoveInputSource(isr)
2453      InputSourceRef isr;
2454 {
2455     InputSource *is = (InputSource *) isr;
2456
2457     if (is->sid == 0) return;
2458     g_source_remove(is->sid);
2459     is->sid = 0;
2460     return;
2461 }
2462
2463 #ifndef HAVE_USLEEP
2464
2465 static Boolean frameWaiting;
2466
2467 static RETSIGTYPE
2468 FrameAlarm (int sig)
2469 {
2470   frameWaiting = False;
2471   /* In case System-V style signals.  Needed?? */
2472   signal(SIGALRM, FrameAlarm);
2473 }
2474
2475 void
2476 FrameDelay (int time)
2477 {
2478   struct itimerval delay;
2479
2480   if (time > 0) {
2481     frameWaiting = True;
2482     signal(SIGALRM, FrameAlarm);
2483     delay.it_interval.tv_sec =
2484       delay.it_value.tv_sec = time / 1000;
2485     delay.it_interval.tv_usec =
2486       delay.it_value.tv_usec = (time % 1000) * 1000;
2487     setitimer(ITIMER_REAL, &delay, NULL);
2488     while (frameWaiting) pause();
2489     delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2490     delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2491     setitimer(ITIMER_REAL, &delay, NULL);
2492   }
2493 }
2494
2495 #else
2496
2497 void
2498 FrameDelay (int time)
2499 {
2500 #ifdef TODO_GTK
2501   XSync(xDisplay, False);
2502 #endif
2503   if (time > 0)
2504     usleep(time * 1000);
2505 }
2506
2507 #endif
2508
2509 static void
2510 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2511 {
2512     char buf[MSG_SIZ], *logoName = buf;
2513     if(appData.logo[n][0]) {
2514         logoName = appData.logo[n];
2515     } else if(appData.autoLogo) {
2516         if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2517             sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2518         } else if(appData.directory[n] && appData.directory[n][0]) {
2519             sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2520         }
2521     }
2522     if(logoName[0])
2523         { ASSIGN(cps->programLogo, logoName); }
2524 }
2525
2526 void
2527 UpdateLogos (int displ)
2528 {
2529     if(optList[W_WHITE-1].handle == NULL) return;
2530     LoadLogo(&first, 0, 0);
2531     LoadLogo(&second, 1, appData.icsActive);
2532     if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2533     return;
2534 }
2535