a86877fd2c647787f30f60a92829972e455622e0
[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         if(flash_delay) do_flash_delay(flash_delay);
1721 }
1722
1723 double
1724 Fraction (int x, int start, int stop)
1725 {
1726    double f = ((double) x - start)/(stop - start);
1727    if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1728    return f;
1729 }
1730
1731 static WindowPlacement wpNew;
1732
1733 #ifdef TODO_GTK
1734 void
1735 CoDrag (Widget sh, WindowPlacement *wp)
1736 {
1737     Arg args[16];
1738     int j=0, touch=0, fudge = 2;
1739     GetActualPlacement(sh, wp);
1740     if(abs(wpMain.x + wpMain.width + 2*frameX - wp->x)         < fudge) touch = 1; else // right touch
1741     if(abs(wp->x + wp->width + 2*frameX - wpMain.x)            < fudge) touch = 2; else // left touch
1742     if(abs(wpMain.y + wpMain.height + frameX + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1743     if(abs(wp->y + wp->height + frameX + frameY - wpMain.y)    < fudge) touch = 4;      // top touch
1744     if(!touch ) return; // only windows that touch co-move
1745     if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1746         int heightInc = wpNew.height - wpMain.height;
1747         double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1748         double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1749         wp->y += fracTop * heightInc;
1750         heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1751         if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1752     } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1753         int widthInc = wpNew.width - wpMain.width;
1754         double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1755         double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1756         wp->y += fracLeft * widthInc;
1757         widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1758         if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1759     }
1760     wp->x += wpNew.x - wpMain.x;
1761     wp->y += wpNew.y - wpMain.y;
1762     if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1763     if(touch == 3) wp->y += wpNew.height - wpMain.height;
1764 #ifdef TODO_GTK
1765     XtSetArg(args[j], XtNx, wp->x); j++;
1766     XtSetArg(args[j], XtNy, wp->y); j++;
1767     XtSetValues(sh, args, j);
1768 #endif
1769 }
1770
1771 void
1772 ReSize (WindowPlacement *wp)
1773 {
1774         int sqx, sqy, w, h;
1775         if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1776         sqx = (wp->width  - lineGap - marginW) / BOARD_WIDTH - lineGap;
1777         sqy = (wp->height - lineGap - marginH) / BOARD_HEIGHT - lineGap;
1778         if(sqy < sqx) sqx = sqy;
1779         if(sqx != squareSize) {
1780             squareSize = sqx; // adopt new square size
1781             CreatePNGPieces(); // make newly scaled pieces
1782             InitDrawingSizes(0, 0); // creates grid etc.
1783         } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1784         w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1785         h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1786         if(optList[W_BOARD].max   > w) optList[W_BOARD].max = w;
1787         if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1788 }
1789
1790 #ifdef TODO_GTK
1791 static XtIntervalId delayedDragID = 0;
1792 #else
1793 static int delayedDragID = 0;
1794 #endif
1795
1796 void
1797 DragProc ()
1798 {
1799         static int busy;
1800         if(busy) return;
1801
1802         busy = 1;
1803         GetActualPlacement(shellWidget, &wpNew);
1804         if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1805            wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1806             busy = 0; return; // false alarm
1807         }
1808         ReSize(&wpNew);
1809         if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1810         if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1811         if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1812         if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1813         wpMain = wpNew;
1814         DrawPosition(True, NULL);
1815         delayedDragID = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1816         busy = 0;
1817 }
1818 #endif
1819
1820 void
1821 DelayedDrag ()
1822 {
1823 #ifdef TODO_GTK
1824     if(delayedDragID) XtRemoveTimeOut(delayedDragID); // cancel pending
1825     delayedDragID =
1826       XtAppAddTimeOut(appContext, 200, (XtTimerCallbackProc) DragProc, (XtPointer) 0); // and schedule new one 50 msec later
1827 #endif
1828 }
1829
1830 #ifdef TODO_GTK
1831 void
1832 EventProc (Widget widget, caddr_t unused, XEvent *event)
1833 {
1834     if(XtIsRealized(widget) && event->type == ConfigureNotify || appData.useStickyWindows)
1835         DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1836 }
1837 #endif
1838
1839 /*
1840  * event handler for redrawing the board
1841  */
1842 #ifdef TODO_GTK
1843 void
1844 DrawPositionProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1845 {
1846     DrawPosition(True, NULL);
1847 }
1848 #endif
1849
1850
1851 static int savedIndex;  /* gross that this is global */
1852
1853 #ifdef TODO_GTK
1854 void
1855 CommentClick (Widget w, XEvent * event, String * params, Cardinal * nParams)
1856 {
1857         String val;
1858         XawTextPosition index, dummy;
1859         Arg arg;
1860
1861         XawTextGetSelectionPos(w, &index, &dummy);
1862         XtSetArg(arg, XtNstring, &val);
1863         XtGetValues(w, &arg, 1);
1864         ReplaceComment(savedIndex, val);
1865         if(savedIndex != currentMove) ToNrEvent(savedIndex);
1866         LoadVariation( index, val ); // [HGM] also does the actual moving to it, now
1867 }
1868 #endif
1869
1870 void
1871 EditCommentPopUp (int index, char *title, char *text)
1872 {
1873     savedIndex = index;
1874     if (text == NULL) text = "";
1875     NewCommentPopup(title, text, index);
1876 }
1877
1878 void
1879 CommentPopUp (char *title, char *text)
1880 {
1881     savedIndex = currentMove; // [HGM] vari
1882     NewCommentPopup(title, text, currentMove);
1883 }
1884
1885 void
1886 CommentPopDown ()
1887 {
1888     PopDown(CommentDlg);
1889 }
1890
1891
1892 /* Disable all user input other than deleting the window */
1893 static int frozen = 0;
1894
1895 void
1896 FreezeUI ()
1897 {
1898   if (frozen) return;
1899   /* Grab by a widget that doesn't accept input */
1900   gtk_grab_add(optList[W_MESSG].handle);
1901   frozen = 1;
1902 }
1903
1904 /* Undo a FreezeUI */
1905 void
1906 ThawUI ()
1907 {
1908   if (!frozen) return;
1909   gtk_grab_remove(optList[W_MESSG].handle);
1910   frozen = 0;
1911 }
1912
1913 void
1914 ModeHighlight ()
1915 {
1916     static int oldPausing = FALSE;
1917     static GameMode oldmode = (GameMode) -1;
1918     char *wname;
1919     if (!boardWidget) return;
1920
1921     if (pausing != oldPausing) {
1922         oldPausing = pausing;
1923         MarkMenuItem("Mode.Pause", pausing);
1924
1925         if (appData.showButtonBar) {
1926           /* Always toggle, don't set.  Previous code messes up when
1927              invoked while the button is pressed, as releasing it
1928              toggles the state again. */
1929             GdkColor color;     
1930             gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1931             gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1932         }
1933     }
1934
1935     wname = ModeToWidgetName(oldmode);
1936     if (wname != NULL) {
1937         MarkMenuItem(wname, False);
1938     }
1939     wname = ModeToWidgetName(gameMode);
1940     if (wname != NULL) {
1941         MarkMenuItem(wname, True);
1942     }
1943     oldmode = gameMode;
1944     MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1945
1946     /* Maybe all the enables should be handled here, not just this one */
1947     EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1948
1949     DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1950 }
1951
1952
1953 /*
1954  * Button/menu procedures
1955  */
1956
1957 #ifdef TODO_GTK
1958 /* this variable is shared between CopyPositionProc and SendPositionSelection */
1959 char *selected_fen_position=NULL;
1960
1961 Boolean
1962 SendPositionSelection (Widget w, Atom *selection, Atom *target,
1963                        Atom *type_return, XtPointer *value_return,
1964                        unsigned long *length_return, int *format_return)
1965 {
1966   char *selection_tmp;
1967
1968 //  if (!selected_fen_position) return False; /* should never happen */
1969   if (*target == XA_STRING || *target == XA_UTF8_STRING(xDisplay)){
1970    if (!selected_fen_position) { // since it never happens, we use it for indicating a game is being sent
1971     FILE* f = fopen(gameCopyFilename, "r"); // This code, taken from SendGameSelection, now merges the two
1972     long len;
1973     size_t count;
1974     if (f == NULL) return False;
1975     fseek(f, 0, 2);
1976     len = ftell(f);
1977     rewind(f);
1978     selection_tmp = XtMalloc(len + 1);
1979     count = fread(selection_tmp, 1, len, f);
1980     fclose(f);
1981     if (len != count) {
1982       XtFree(selection_tmp);
1983       return False;
1984     }
1985     selection_tmp[len] = NULLCHAR;
1986    } else {
1987     /* note: since no XtSelectionDoneProc was registered, Xt will
1988      * automatically call XtFree on the value returned.  So have to
1989      * make a copy of it allocated with XtMalloc */
1990     selection_tmp= XtMalloc(strlen(selected_fen_position)+16);
1991     safeStrCpy(selection_tmp, selected_fen_position, strlen(selected_fen_position)+16 );
1992    }
1993
1994     *value_return=selection_tmp;
1995     *length_return=strlen(selection_tmp);
1996     *type_return=*target;
1997     *format_return = 8; /* bits per byte */
1998     return True;
1999   } else if (*target == XA_TARGETS(xDisplay)) {
2000     Atom *targets_tmp = (Atom *) XtMalloc(2 * sizeof(Atom));
2001     targets_tmp[0] = XA_UTF8_STRING(xDisplay);
2002     targets_tmp[1] = XA_STRING;
2003     *value_return = targets_tmp;
2004     *type_return = XA_ATOM;
2005     *length_return = 2;
2006 #if 0
2007     // This code leads to a read of value_return out of bounds on 64-bit systems.
2008     // Other code which I have seen always sets *format_return to 32 independent of
2009     // sizeof(Atom) without adjusting *length_return. For instance see TextConvertSelection()
2010     // at http://cgit.freedesktop.org/xorg/lib/libXaw/tree/src/Text.c -- BJ
2011     *format_return = 8 * sizeof(Atom);
2012     if (*format_return > 32) {
2013       *length_return *= *format_return / 32;
2014       *format_return = 32;
2015     }
2016 #else
2017     *format_return = 32;
2018 #endif
2019     return True;
2020   } else {
2021     return False;
2022   }
2023 }
2024 #endif
2025
2026 /* note: when called from menu all parameters are NULL, so no clue what the
2027  * Widget which was clicked on was, or what the click event was
2028  */
2029 void
2030 CopySomething (char *src)
2031 {
2032 #ifdef TODO_GTK
2033     selected_fen_position = src;
2034     /*
2035      * Set both PRIMARY (the selection) and CLIPBOARD, since we don't
2036      * have a notion of a position that is selected but not copied.
2037      * See http://www.freedesktop.org/wiki/Specifications/ClipboardsWiki
2038      */
2039     XtOwnSelection(menuBarWidget, XA_PRIMARY,
2040                    CurrentTime,
2041                    SendPositionSelection,
2042                    NULL/* lose_ownership_proc */ ,
2043                    NULL/* transfer_done_proc */);
2044     XtOwnSelection(menuBarWidget, XA_CLIPBOARD(xDisplay),
2045                    CurrentTime,
2046                    SendPositionSelection,
2047                    NULL/* lose_ownership_proc */ ,
2048                    NULL/* transfer_done_proc */);
2049 #endif
2050 }
2051
2052 #ifdef TODO_GTK
2053 /* function called when the data to Paste is ready */
2054 static void
2055 PastePositionCB (Widget w, XtPointer client_data, Atom *selection,
2056                  Atom *type, XtPointer value, unsigned long *len, int *format)
2057 {
2058   char *fenstr=value;
2059   if (value==NULL || *len==0) return; /* nothing had been selected to copy */
2060   fenstr[*len]='\0'; /* normally this string is terminated, but be safe */
2061   EditPositionPasteFEN(fenstr);
2062   XtFree(value);
2063 }
2064 #endif
2065
2066 /* called when Paste Position button is pressed,
2067  * all parameters will be NULL */
2068 void
2069 PastePositionProc ()
2070 {
2071 #ifdef TODO_GTK
2072     XtGetSelectionValue(menuBarWidget,
2073       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
2074       /* (XtSelectionCallbackProc) */ PastePositionCB,
2075       NULL, /* client_data passed to PastePositionCB */
2076
2077       /* better to use the time field from the event that triggered the
2078        * call to this function, but that isn't trivial to get
2079        */
2080       CurrentTime
2081     );
2082     return;
2083 #endif
2084 }
2085
2086 #ifdef TODO_GTK
2087 /* note: when called from menu all parameters are NULL, so no clue what the
2088  * Widget which was clicked on was, or what the click event was
2089  */
2090 /* function called when the data to Paste is ready */
2091 static void
2092 PasteGameCB (Widget w, XtPointer client_data, Atom *selection,
2093              Atom *type, XtPointer value, unsigned long *len, int *format)
2094 {
2095   FILE* f;
2096   if (value == NULL || *len == 0) {
2097     return; /* nothing had been selected to copy */
2098   }
2099   f = fopen(gamePasteFilename, "w");
2100   if (f == NULL) {
2101     DisplayError(_("Can't open temp file"), errno);
2102     return;
2103   }
2104   fwrite(value, 1, *len, f);
2105   fclose(f);
2106   XtFree(value);
2107   LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
2108 }
2109 #endif
2110
2111 /* called when Paste Game button is pressed,
2112  * all parameters will be NULL */
2113 void
2114 PasteGameProc ()
2115 {
2116 #ifdef TODO_GTK
2117     XtGetSelectionValue(menuBarWidget,
2118       appData.pasteSelection ? XA_PRIMARY: XA_CLIPBOARD(xDisplay), XA_STRING,
2119       /* (XtSelectionCallbackProc) */ PasteGameCB,
2120       NULL, /* client_data passed to PasteGameCB */
2121
2122       /* better to use the time field from the event that triggered the
2123        * call to this function, but that isn't trivial to get
2124        */
2125       CurrentTime
2126     );
2127     return;
2128 #endif
2129 }
2130
2131
2132 #ifdef TODO_GTK
2133 void
2134 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2135 {
2136     QuitProc();
2137 }
2138 #endif
2139
2140 int
2141 ShiftKeys ()
2142 {   // bassic primitive for determining if modifier keys are pressed
2143     int i,j,  k=0;
2144 #ifdef TODO_GTK
2145     long int codes[] = { XK_Meta_L, XK_Meta_R, XK_Control_L, XK_Control_R, XK_Shift_L, XK_Shift_R };
2146     char keys[32];
2147     XQueryKeymap(xDisplay,keys);
2148     for(i=0; i<6; i++) {
2149         k <<= 1;
2150         j = XKeysymToKeycode(xDisplay, codes[i]);
2151         k += ( (keys[j>>3]&1<<(j&7)) != 0 );
2152     }
2153 #endif
2154     return k;
2155 }
2156
2157 void MoveTypeInProc(eventkey)
2158     GdkEventKey  *eventkey;
2159 {
2160     char buf[10];
2161
2162     // ingnore if ctrl or alt is pressed
2163     if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) {
2164         return;
2165     }
2166
2167     buf[0]=eventkey->keyval;
2168     buf[1]='\0';
2169     if (*buf >= 32)        
2170         BoxAutoPopUp (buf);
2171 }
2172
2173 #ifdef TODO_GTK
2174 void
2175 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2176 {
2177         if (!TempBackwardActive) {
2178                 TempBackwardActive = True;
2179                 BackwardEvent();
2180         }
2181 }
2182
2183 void
2184 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2185 {
2186         /* Check to see if triggered by a key release event for a repeating key.
2187          * If so the next queued event will be a key press of the same key at the same time */
2188         if (XEventsQueued(xDisplay, QueuedAfterReading)) {
2189                 XEvent next;
2190                 XPeekEvent(xDisplay, &next);
2191                 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
2192                         next.xkey.keycode == event->xkey.keycode)
2193                                 return;
2194         }
2195     ForwardEvent();
2196         TempBackwardActive = False;
2197 }
2198
2199 void
2200 ManInner (Widget w, XEvent *event, String *prms, Cardinal *nprms)
2201 {   // called as key binding
2202     char buf[MSG_SIZ];
2203     String name;
2204     if (nprms && *nprms > 0)
2205       name = prms[0];
2206     else
2207       name = "xboard";
2208     snprintf(buf, sizeof(buf), "xterm -e man %s &", name);
2209     system(buf);
2210 }
2211 #endif
2212
2213 void
2214 ManProc ()
2215 {   // called from menu
2216 #ifdef TODO_GTK
2217     ManInner(NULL, NULL, NULL, NULL);
2218 #endif
2219 }
2220
2221 void
2222 SetWindowTitle (char *text, char *title, char *icon)
2223 {
2224 #ifdef TODO_GTK
2225     Arg args[16];
2226     int i;
2227     if (appData.titleInWindow) {
2228         i = 0;
2229         XtSetArg(args[i], XtNlabel, text);   i++;
2230         XtSetValues(titleWidget, args, i);
2231     }
2232     i = 0;
2233     XtSetArg(args[i], XtNiconName, (XtArgVal) icon);    i++;
2234     XtSetArg(args[i], XtNtitle, (XtArgVal) title);      i++;
2235     XtSetValues(shellWidget, args, i);
2236     XSync(xDisplay, False);
2237 #endif
2238     if (appData.titleInWindow) {
2239         SetWidgetLabel(titleWidget, text);
2240     }
2241     gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
2242 }
2243
2244
2245 static int
2246 NullXErrorCheck (Display *dpy, XErrorEvent *error_event)
2247 {
2248     return 0;
2249 }
2250
2251 void
2252 DisplayIcsInteractionTitle (String message)
2253 {
2254 #ifdef TODO_GTK
2255   if (oldICSInteractionTitle == NULL) {
2256     /* Magic to find the old window title, adapted from vim */
2257     char *wina = getenv("WINDOWID");
2258     if (wina != NULL) {
2259       Window win = (Window) atoi(wina);
2260       Window root, parent, *children;
2261       unsigned int nchildren;
2262       int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
2263       for (;;) {
2264         if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
2265         if (!XQueryTree(xDisplay, win, &root, &parent,
2266                         &children, &nchildren)) break;
2267         if (children) XFree((void *)children);
2268         if (parent == root || parent == 0) break;
2269         win = parent;
2270       }
2271       XSetErrorHandler(oldHandler);
2272     }
2273     if (oldICSInteractionTitle == NULL) {
2274       oldICSInteractionTitle = "xterm";
2275     }
2276   }
2277   printf("\033]0;%s\007", message);
2278   fflush(stdout);
2279 #endif
2280 }
2281
2282
2283 void
2284 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
2285 {
2286     GtkWidget *w = (GtkWidget *) opt->handle;
2287     char *markup;
2288     char bgcolor[10];
2289     char fgcolor[10];
2290
2291     if (highlight) {
2292         strcpy(bgcolor, "black");
2293         strcpy(fgcolor, "white");
2294     } else {
2295         strcpy(bgcolor, "white");
2296         strcpy(fgcolor, "black");
2297     }
2298     if (timer > 0 &&
2299         appData.lowTimeWarning &&
2300         (timer / 1000) < appData.icsAlarmTime) {
2301         strcpy(fgcolor, appData.lowTimeWarningColor);
2302     }
2303
2304     if (appData.clockMode) {
2305         markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
2306                                          bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2307     } else {
2308         markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s  </span>",
2309                                          bgcolor, fgcolor, color);
2310     }
2311     gtk_label_set_markup(GTK_LABEL(w), markup);
2312     g_free(markup);
2313 }
2314
2315 #ifdef TODO_GTK
2316 static Pixmap *clockIcons[] = { &wIconPixmap, &bIconPixmap };
2317 #endif
2318
2319 void
2320 SetClockIcon (int color)
2321 {
2322 #ifdef TODO_GTK
2323     Arg args[16];
2324     Pixmap pm = *clockIcons[color];
2325     if (iconPixmap != pm) {
2326         iconPixmap = pm;
2327         XtSetArg(args[0], XtNiconPixmap, iconPixmap);
2328         XtSetValues(shellWidget, args, 1);
2329     }
2330 #endif
2331 }
2332
2333 #define INPUT_SOURCE_BUF_SIZE 8192
2334
2335 typedef struct {
2336     CPKind kind;
2337     int fd;
2338     int lineByLine;
2339     char *unused;
2340     InputCallback func;
2341     guint sid;
2342     char buf[INPUT_SOURCE_BUF_SIZE];
2343     VOIDSTAR closure;
2344 } InputSource;
2345
2346 gboolean
2347 DoInputCallback(io, cond, data)
2348      GIOChannel  *io;
2349      GIOCondition cond;
2350      gpointer    *data;
2351 {
2352   /* read input from one of the input source (for example a chess program, ICS, etc).
2353    * and call a function that will handle the input
2354    */
2355
2356     int count;
2357     int error;
2358     char *p, *q;
2359
2360     /* All information (callback function, file descriptor, etc) is
2361      * saved in an InputSource structure
2362      */
2363     InputSource *is = (InputSource *) data;
2364
2365     if (is->lineByLine) {
2366         count = read(is->fd, is->unused,
2367                      INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
2368         if (count <= 0) {
2369             (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
2370             return True;
2371         }
2372         is->unused += count;
2373         p = is->buf;
2374         /* break input into lines and call the callback function on each
2375          * line
2376          */
2377         while (p < is->unused) {
2378             q = memchr(p, '\n', is->unused - p);
2379             if (q == NULL) break;
2380             q++;
2381             (is->func)(is, is->closure, p, q - p, 0);
2382             p = q;
2383         }
2384         /* remember not yet used part of the buffer */
2385         q = is->buf;
2386         while (p < is->unused) {
2387             *q++ = *p++;
2388         }
2389         is->unused = q;
2390     } else {
2391       /* read maximum length of input buffer and send the whole buffer
2392        * to the callback function
2393        */
2394         count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2395         if (count == -1)
2396           error = errno;
2397         else
2398           error = 0;
2399         (is->func)(is, is->closure, is->buf, count, error);
2400     }
2401     return True; // Must return true or the watch will be removed
2402 }
2403
2404 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2405      ProcRef pr;
2406      int lineByLine;
2407      InputCallback func;
2408      VOIDSTAR closure;
2409 {
2410     InputSource *is;
2411     GIOChannel *channel;
2412     ChildProc *cp = (ChildProc *) pr;
2413
2414     is = (InputSource *) calloc(1, sizeof(InputSource));
2415     is->lineByLine = lineByLine;
2416     is->func = func;
2417     if (pr == NoProc) {
2418         is->kind = CPReal;
2419         is->fd = fileno(stdin);
2420     } else {
2421         is->kind = cp->kind;
2422         is->fd = cp->fdFrom;
2423     }
2424     if (lineByLine)
2425       is->unused = is->buf;
2426     else
2427       is->unused = NULL;
2428
2429    /* GTK-TODO: will this work on windows?*/
2430
2431     channel = g_io_channel_unix_new(is->fd);
2432     g_io_channel_set_close_on_unref (channel, TRUE);
2433     is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2434
2435     is->closure = closure;
2436     return (InputSourceRef) is;
2437 }
2438
2439
2440 void
2441 RemoveInputSource(isr)
2442      InputSourceRef isr;
2443 {
2444     InputSource *is = (InputSource *) isr;
2445
2446     if (is->sid == 0) return;
2447     g_source_remove(is->sid);
2448     is->sid = 0;
2449     return;
2450 }
2451
2452 #ifndef HAVE_USLEEP
2453
2454 static Boolean frameWaiting;
2455
2456 static RETSIGTYPE
2457 FrameAlarm (int sig)
2458 {
2459   frameWaiting = False;
2460   /* In case System-V style signals.  Needed?? */
2461   signal(SIGALRM, FrameAlarm);
2462 }
2463
2464 void
2465 FrameDelay (int time)
2466 {
2467   struct itimerval delay;
2468
2469   if (time > 0) {
2470     frameWaiting = True;
2471     signal(SIGALRM, FrameAlarm);
2472     delay.it_interval.tv_sec =
2473       delay.it_value.tv_sec = time / 1000;
2474     delay.it_interval.tv_usec =
2475       delay.it_value.tv_usec = (time % 1000) * 1000;
2476     setitimer(ITIMER_REAL, &delay, NULL);
2477     while (frameWaiting) pause();
2478     delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2479     delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2480     setitimer(ITIMER_REAL, &delay, NULL);
2481   }
2482 }
2483
2484 #else
2485
2486 void
2487 FrameDelay (int time)
2488 {
2489 #ifdef TODO_GTK
2490   XSync(xDisplay, False);
2491 #endif
2492 //  gtk_main_iteration_do(False);
2493
2494   if (time > 0)
2495     usleep(time * 1000);
2496 }
2497
2498 #endif
2499
2500 static void
2501 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2502 {
2503     char buf[MSG_SIZ], *logoName = buf;
2504     if(appData.logo[n][0]) {
2505         logoName = appData.logo[n];
2506     } else if(appData.autoLogo) {
2507         if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2508             sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2509         } else if(appData.directory[n] && appData.directory[n][0]) {
2510             sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2511         }
2512     }
2513     if(logoName[0])
2514         { ASSIGN(cps->programLogo, logoName); }
2515 }
2516
2517 void
2518 UpdateLogos (int displ)
2519 {
2520     if(optList[W_WHITE-1].handle == NULL) return;
2521     LoadLogo(&first, 0, 0);
2522     LoadLogo(&second, 1, appData.icsActive);
2523     if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2524     return;
2525 }
2526