Integrate ICS output into Chat Window
[xboard.git] / gtk / 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, 2013, 2014 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 "menus.h"
163 #include "board.h"
164 #include "dialogs.h"
165 #include "engineoutput.h"
166 #include "usystem.h"
167 #include "gettext.h"
168 #include "draw.h"
169
170 #ifdef __APPLE__
171 #  include <gtkmacintegration/gtkosxapplication.h>
172    // prevent pathname of positional file argument provided by OS X being be mistaken for option name
173    // (price is that we won't recognize Windows option format anymore).
174 #  define SLASH '-'
175    // redefine some defaults
176 #  undef ICS_LOGON
177 #  undef DATADIR
178 #  undef SETTINGS_FILE
179 #  define ICS_LOGON "Library/Preferences/XboardICS.conf"
180 #  define DATADIR dataDir
181 #  define SETTINGS_FILE masterSettings
182    char dataDir[MSG_SIZ]; // for expanding ~~
183    char masterSettings[MSG_SIZ];
184 #else
185 #  define SLASH '/'
186 #endif
187
188 #ifdef __EMX__
189 #ifndef HAVE_USLEEP
190 #define HAVE_USLEEP
191 #endif
192 #define usleep(t)   _sleep2(((t)+500)/1000)
193 #endif
194
195 #ifdef ENABLE_NLS
196 # define  _(s) gettext (s)
197 # define N_(s) gettext_noop (s)
198 #else
199 # define  _(s) (s)
200 # define N_(s)  s
201 #endif
202
203 int main P((int argc, char **argv));
204 RETSIGTYPE CmailSigHandler P((int sig));
205 RETSIGTYPE IntSigHandler P((int sig));
206 RETSIGTYPE TermSizeSigHandler P((int sig));
207 #if ENABLE_NLS
208 char *InsertPxlSize P((char *pattern, int targetPxlSize));
209 XFontSet CreateFontSet P((char *base_fnt_lst));
210 #else
211 char *FindFont P((char *pattern, int targetPxlSize));
212 #endif
213 void DelayedDrag P((void));
214 void ICSInputBoxPopUp P((void));
215 void MoveTypeInProc P((GdkEventKey *eventkey));
216 gboolean KeyPressProc P((GtkWindow *window, GdkEventKey *eventkey, gpointer data));
217 Boolean TempBackwardActive = False;
218 void DisplayMove P((int moveNumber));
219 void update_ics_width P(());
220 int CopyMemoProc P(());
221 static gboolean EventProc P((GtkWidget *widget, GdkEvent *event, gpointer g));
222
223 #ifdef TODO_GTK
224 #if ENABLE_NLS
225 XFontSet fontSet, clockFontSet;
226 #else
227 Font clockFontID;
228 XFontStruct *clockFontStruct;
229 #endif
230 Font coordFontID, countFontID;
231 XFontStruct *coordFontStruct, *countFontStruct;
232 #else
233 void *shellWidget, *formWidget, *boardWidget, *titleWidget, *dropMenu, *menuBarWidget;
234 GtkWidget       *mainwindow;
235 #endif
236 Option *optList; // contains all widgets of main window
237 char *layoutName;
238
239 char installDir[] = "."; // [HGM] UCI: needed for UCI; probably needs run-time initializtion
240
241 /* pixbufs */
242 static GdkPixbuf       *mainwindowIcon=NULL;
243 static GdkPixbuf       *WhiteIcon=NULL;
244 static GdkPixbuf       *BlackIcon=NULL;
245
246 /* key board accelerators */
247 GtkAccelGroup *GtkAccelerators;
248
249 typedef unsigned int BoardSize;
250 BoardSize boardSize;
251 Boolean chessProgram;
252
253 int  minX, minY; // [HGM] placement: volatile limits on upper-left corner
254 int smallLayout = 0, tinyLayout = 0,
255   marginW, marginH, // [HGM] for run-time resizing
256   fromX = -1, fromY = -1, toX, toY, commentUp = False,
257   errorExitStatus = -1, defaultLineGap;
258 #ifdef TODO_GTK
259 Dimension textHeight;
260 #endif
261 char *chessDir, *programName, *programVersion;
262 Boolean alwaysOnTop = False;
263 char *icsTextMenuString;
264 char *icsNames;
265 char *firstChessProgramNames;
266 char *secondChessProgramNames;
267
268 WindowPlacement wpMain;
269 WindowPlacement wpConsole;
270 WindowPlacement wpComment;
271 WindowPlacement wpMoveHistory;
272 WindowPlacement wpEvalGraph;
273 WindowPlacement wpEngineOutput;
274 WindowPlacement wpGameList;
275 WindowPlacement wpTags;
276 WindowPlacement wpDualBoard;
277
278 /* This magic number is the number of intermediate frames used
279    in each half of the animation. For short moves it's reduced
280    by 1. The total number of frames will be factor * 2 + 1.  */
281 #define kFactor    4
282
283 SizeDefaults sizeDefaults[] = SIZE_DEFAULTS;
284
285 typedef struct {
286     char piece;
287     char* widget;
288 } DropMenuEnables;
289
290 DropMenuEnables dmEnables[] = {
291     { 'P', "Pawn" },
292     { 'N', "Knight" },
293     { 'B', "Bishop" },
294     { 'R', "Rook" },
295     { 'Q', "Queen" }
296 };
297
298 #ifdef TODO_GTK
299 XtResource clientResources[] = {
300     { "flashCount", "flashCount", XtRInt, sizeof(int),
301         XtOffset(AppDataPtr, flashCount), XtRImmediate,
302         (XtPointer) FLASH_COUNT  },
303 };
304 #endif
305
306 /* keyboard shortcuts not yet transistioned int menuitem @ menu.c */
307 char globalTranslations[] =
308   ":Ctrl<Key>Down: LoadSelectedProc(3) \n \
309    :Ctrl<Key>Up: LoadSelectedProc(-3) \n \
310    :<KeyDown>Return: TempBackwardProc() \n \
311    :<KeyUp>Return: TempForwardProc() \n";
312
313 char ICSInputTranslations[] =
314     "<Key>Up: UpKeyProc() \n "
315     "<Key>Down: DownKeyProc() \n "
316     "<Key>Return: EnterKeyProc() \n";
317
318 // [HGM] vari: another hideous kludge: call extend-end first so we can be sure select-start works,
319 //             as the widget is destroyed before the up-click can call extend-end
320 char commentTranslations[] = "<Btn3Down>: extend-end() select-start() CommentClick() \n";
321
322 #ifdef TODO_GTK
323 String xboardResources[] = {
324     "*Error*translations: #override\\n <Key>Return: ErrorPopDown()",
325     NULL
326   };
327 #endif
328
329 void
330 BoardToTop ()
331 {
332   gtk_window_present(GTK_WINDOW(shells[BoardWindow]));
333 }
334
335 //---------------------------------------------------------------------------------------------------------
336 // some symbol definitions to provide the proper (= XBoard) context for the code in args.h
337 #define XBOARD True
338 #define JAWS_ARGS
339 #define CW_USEDEFAULT (1<<31)
340 #define ICS_TEXT_MENU_SIZE 90
341 #define DEBUG_FILE "xboard.debug"
342 #define SetCurrentDirectory chdir
343 #define GetCurrentDirectory(SIZE, NAME) getcwd(NAME, SIZE)
344 #define OPTCHAR "-"
345 #define SEPCHAR " "
346
347 // The option definition and parsing code common to XBoard and WinBoard is collected in this file
348 #include "args.h"
349
350 // front-end part of option handling
351
352 // [HGM] This platform-dependent table provides the location for storing the color info
353 extern char *crWhite, * crBlack;
354
355 void *
356 colorVariable[] = {
357   &appData.whitePieceColor,
358   &appData.blackPieceColor,
359   &appData.lightSquareColor,
360   &appData.darkSquareColor,
361   &appData.highlightSquareColor,
362   &appData.premoveHighlightColor,
363   &appData.lowTimeWarningColor,
364   NULL,
365   NULL,
366   NULL,
367   NULL,
368   NULL,
369   &crWhite,
370   &crBlack,
371   NULL
372 };
373
374 // [HGM] font: keep a font for each square size, even non-stndard ones
375 #define NUM_SIZES 18
376 #define MAX_SIZE 130
377 Boolean fontIsSet[NUM_FONTS], fontValid[NUM_FONTS][MAX_SIZE];
378 char *fontTable[NUM_FONTS][MAX_SIZE];
379
380 void
381 ParseFont (char *name, int number)
382 { // in XBoard, only 2 of the fonts are currently implemented, and we just copy their name
383   int size;
384   if(sscanf(name, "size%d:", &size)) {
385     // [HGM] font: font is meant for specific boardSize (likely from settings file);
386     //       defer processing it until we know if it matches our board size
387     if(size >= 0 && size<MAX_SIZE) { // for now, fixed limit
388         fontTable[number][size] = strdup(strchr(name, ':')+1);
389         fontValid[number][size] = True;
390     }
391     return;
392   }
393   switch(number) {
394     case 0: // CLOCK_FONT
395         appData.clockFont = strdup(name);
396       break;
397     case 1: // MESSAGE_FONT
398         appData.font = strdup(name);
399       break;
400     case 2: // COORD_FONT
401         appData.coordFont = strdup(name);
402       break;
403     default:
404       return;
405   }
406   fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
407 }
408
409 void
410 SetFontDefaults ()
411 { // only 2 fonts currently
412   appData.clockFont = CLOCK_FONT_NAME;
413   appData.coordFont = COORD_FONT_NAME;
414   appData.font  =   DEFAULT_FONT_NAME;
415 }
416
417 void
418 CreateFonts ()
419 { // no-op, until we identify the code for this already in XBoard and move it here
420 }
421
422 void
423 ParseColor (int n, char *name)
424 { // in XBoard, just copy the color-name string
425   if(colorVariable[n]) *(char**)colorVariable[n] = strdup(name);
426 }
427
428 void
429 ParseTextAttribs (ColorClass cc, char *s)
430 {
431     (&appData.colorShout)[cc] = strdup(s);
432 }
433
434 void
435 ParseBoardSize (void *addr, char *name)
436 {
437     appData.boardSize = strdup(name);
438 }
439
440 void
441 LoadAllSounds ()
442 { // In XBoard the sound-playing program takes care of obtaining the actual sound
443 }
444
445 void
446 SetCommPortDefaults ()
447 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
448 }
449
450 // [HGM] args: these three cases taken out to stay in front-end
451 void
452 SaveFontArg (FILE *f, ArgDescriptor *ad)
453 {
454   char *name;
455   int i, n = (int)(intptr_t)ad->argLoc;
456   switch(n) {
457     case 0: // CLOCK_FONT
458         name = appData.clockFont;
459       break;
460     case 1: // MESSAGE_FONT
461         name = appData.font;
462       break;
463     case 2: // COORD_FONT
464         name = appData.coordFont;
465       break;
466     default:
467       return;
468   }
469   for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
470     if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
471         fontTable[n][squareSize] = strdup(name);
472         fontValid[n][squareSize] = True;
473         break;
474   }
475   for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
476     fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
477 }
478
479 void
480 ExportSounds ()
481 { // nothing to do, as the sounds are at all times represented by their text-string names already
482 }
483
484 void
485 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
486 {       // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
487         fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
488 }
489
490 void
491 SaveColor (FILE *f, ArgDescriptor *ad)
492 {       // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
493         if(colorVariable[(int)(intptr_t)ad->argLoc])
494         fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
495 }
496
497 void
498 SaveBoardSize (FILE *f, char *name, void *addr)
499 { // wrapper to shield back-end from BoardSize & sizeInfo
500   fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
501 }
502
503 void
504 ParseCommPortSettings (char *s)
505 { // no such option in XBoard (yet)
506 }
507
508 int frameX, frameY;
509
510 void
511 GetActualPlacement (GtkWidget *shell, WindowPlacement *wp)
512 {
513   GtkAllocation a;
514   if(!shell) return;
515   gtk_widget_get_allocation(shell, &a);
516   gtk_window_get_position(GTK_WINDOW(shell), &a.x, &a.y);
517   wp->x = a.x;
518   wp->y = a.y;
519   wp->width = a.width;
520   wp->height = a.height;
521 //printf("placement: (%d,%d) %dx%d\n", a.x, a.y, a.width, a.height);
522   frameX = 3; frameY = 3; // remember to decide if windows touch
523 }
524
525 void
526 GetWindowCoords ()
527 { // wrapper to shield use of window handles from back-end (make addressible by number?)
528   // In XBoard this will have to wait until awareness of window parameters is implemented
529   GetActualPlacement(shellWidget, &wpMain);
530   if(shellUp[EngOutDlg]) GetActualPlacement(shells[EngOutDlg], &wpEngineOutput);
531   if(shellUp[HistoryDlg]) GetActualPlacement(shells[HistoryDlg], &wpMoveHistory);
532   if(shellUp[EvalGraphDlg]) GetActualPlacement(shells[EvalGraphDlg], &wpEvalGraph);
533   if(shellUp[GameListDlg]) GetActualPlacement(shells[GameListDlg], &wpGameList);
534   if(shellUp[CommentDlg]) GetActualPlacement(shells[CommentDlg], &wpComment);
535   if(shellUp[TagsDlg]) GetActualPlacement(shells[TagsDlg], &wpTags);
536   GetPlacement(ChatDlg, &wpConsole); if(appData.icsActive) wpConsole.visible = shellUp[ChatDlg];
537 }
538
539 void
540 PrintCommPortSettings (FILE *f, char *name)
541 { // This option does not exist in XBoard
542 }
543
544 void
545 EnsureOnScreen (int *x, int *y, int minX, int minY)
546 {
547   return;
548 }
549
550 int
551 MainWindowUp ()
552 { // [HGM] args: allows testing if main window is realized from back-end
553   return DialogExists(BoardWindow);
554 }
555
556 void
557 PopUpStartupDialog ()
558 {  // start menu not implemented in XBoard
559 }
560
561 char *
562 ConvertToLine (int argc, char **argv)
563 {
564   static char line[128*1024], buf[1024];
565   int i;
566
567   line[0] = NULLCHAR;
568   for(i=1; i<argc; i++)
569     {
570       if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
571           && argv[i][0] != '{' )
572         snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
573       else
574         snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
575       strncat(line, buf, 128*1024 - strlen(line) - 1 );
576     }
577
578   line[strlen(line)-1] = NULLCHAR;
579   return line;
580 }
581
582 //--------------------------------------------------------------------------------------------
583
584 int clockKludge;
585
586 void
587 ResizeBoardWindow (int w, int h, int inhibit)
588 {
589     GtkAllocation a;
590     if(clockKludge) return; // ignore as long as clock does not have final height
591     gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
592     w += marginW + 1; // [HGM] not sure why the +1 is (sometimes) needed...
593     h += marginH + a.height + 1;
594     gtk_window_resize(GTK_WINDOW(shellWidget), w, h);
595 }
596
597 int
598 MakeColors ()
599 {   // dummy, as the GTK code does not make colors in advance
600     return FALSE;
601 }
602
603 void
604 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
605 {   // determine what fonts to use, and create them
606 #ifdef TODO_GTK
607     XrmValue vTo;
608     XrmDatabase xdb;
609
610     if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
611         appData.clockFont = fontTable[CLOCK_FONT][squareSize];
612     if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
613         appData.font = fontTable[MESSAGE_FONT][squareSize];
614     if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
615         appData.coordFont = fontTable[COORD_FONT][squareSize];
616
617 #if ENABLE_NLS
618     appData.font = InsertPxlSize(appData.font, fontPxlSize);
619     appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
620     appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
621     fontSet = CreateFontSet(appData.font);
622     clockFontSet = CreateFontSet(appData.clockFont);
623     {
624       /* For the coordFont, use the 0th font of the fontset. */
625       XFontSet coordFontSet = CreateFontSet(appData.coordFont);
626       XFontStruct **font_struct_list;
627       XFontSetExtents *fontSize;
628       char **font_name_list;
629       XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
630       coordFontID = XLoadFont(xDisplay, font_name_list[0]);
631       coordFontStruct = XQueryFont(xDisplay, coordFontID);
632       fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
633       textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
634     }
635 #else
636     appData.font = FindFont(appData.font, fontPxlSize);
637     appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
638     appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
639     clockFontID = XLoadFont(xDisplay, appData.clockFont);
640     clockFontStruct = XQueryFont(xDisplay, clockFontID);
641     coordFontID = XLoadFont(xDisplay, appData.coordFont);
642     coordFontStruct = XQueryFont(xDisplay, coordFontID);
643     // textHeight in !NLS mode!
644 #endif
645     countFontID = coordFontID;  // [HGM] holdings
646     countFontStruct = coordFontStruct;
647
648     xdb = XtDatabase(xDisplay);
649 #if ENABLE_NLS
650     XrmPutLineResource(&xdb, "*international: True");
651     vTo.size = sizeof(XFontSet);
652     vTo.addr = (XtPointer) &fontSet;
653     XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
654 #else
655     XrmPutStringResource(&xdb, "*font", appData.font);
656 #endif
657 #endif
658 }
659
660 char *
661 PrintArg (ArgType t)
662 {
663   char *p="";
664   switch(t) {
665     case ArgZ:
666     case ArgInt:      p = " N"; break;
667     case ArgString:   p = " STR"; break;
668     case ArgBoolean:  p = " TF"; break;
669     case ArgSettingsFilename:
670     case ArgBackupSettingsFile:
671     case ArgFilename: p = " FILE"; break;
672     case ArgX:        p = " Nx"; break;
673     case ArgY:        p = " Ny"; break;
674     case ArgAttribs:  p = " TEXTCOL"; break;
675     case ArgColor:    p = " COL"; break;
676     case ArgFont:     p = " FONT"; break;
677     case ArgBoardSize: p = " SIZE"; break;
678     case ArgFloat: p = " FLOAT"; break;
679     case ArgTrue:
680     case ArgFalse:
681     case ArgTwo:
682     case ArgNone:
683     case ArgCommSettings:
684     default:
685       break;
686   }
687   return p;
688 }
689
690 void
691 PrintOptions ()
692 {
693   char buf[MSG_SIZ];
694   int len=0;
695   ArgDescriptor *q, *p = argDescriptors+5;
696   printf("\nXBoard accepts the following options:\n"
697          "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
698          " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
699          " SIZE = board-size spec(s)\n"
700          " Within parentheses are short forms, or options to set to true or false.\n"
701          " Persistent options (saved in the settings file) are marked with *)\n\n");
702   while(p->argName) {
703     if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
704     snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
705     if(p->save) strcat(buf+len, "*");
706     for(q=p+1; q->argLoc == p->argLoc; q++) {
707       if(q->argName[0] == '-') continue;
708       strcat(buf+len, q == p+1 ? " (" : " ");
709       sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
710     }
711     if(q != p+1) strcat(buf+len, ")");
712     len = strlen(buf);
713     if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
714     p = q;
715   }
716   if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
717 }
718
719 void
720 SlaveResize (Option *opt)
721 {
722     static int slaveW, slaveH, w, h;
723     GtkAllocation a;
724     if(!slaveH) {
725         gtk_widget_get_allocation(shells[DummyDlg], &a);
726         w = a.width; h = a.height;
727         gtk_widget_get_allocation(opt->handle, &a);
728         slaveW =  w - opt->max; // [HGM] needed to set new shellWidget size when we resize board
729         slaveH =  h - a.height + 13;
730    }
731   gtk_window_resize(GTK_WINDOW(shells[DummyDlg]), slaveW + opt->max, slaveH + opt->value);
732 }
733
734 #ifdef __APPLE__
735 static char clickedFile[MSG_SIZ];
736 static int suppress;
737
738 static gboolean
739 StartNewXBoard(GtkosxApplication *app, gchar *path, gpointer user_data)
740 { // handler of OSX OpenFile signal, which sends us the filename of clicked file or first argument
741   if(suppress) { // we just started XBoard without arguments
742     strncpy(clickedFile, path, MSG_SIZ); // remember file name, but otherwise ignore
743   } else {       // we are running something presumably useful
744     char buf[MSG_SIZ];
745     snprintf(buf, MSG_SIZ, "open -n -a \"xboard\" --args \"%s\"", path);
746     system(buf); // start new instance on this file
747   }
748   return TRUE;
749 }
750 #endif
751
752 int
753 main (int argc, char **argv)
754 {
755     int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
756     int boardWidth, w, h; //, boardHeight;
757     char *p;
758     int forceMono = False;
759
760     srandom(time(0)); // [HGM] book: make random truly random
761
762     setbuf(stdout, NULL);
763     setbuf(stderr, NULL);
764     debugFP = stderr;
765
766     if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
767         printf("%s version %s\n\n  configure options: %s\n", PACKAGE_NAME, PACKAGE_VERSION, CONFIGURE_OPTIONS);
768         exit(0);
769     }
770
771     if(argc > 1 && !strcmp(argv[1], "--help" )) {
772         PrintOptions();
773         exit(0);
774     }
775
776     /* set up GTK */
777     gtk_init (&argc, &argv);
778 #ifdef __APPLE__
779     {   // prepare to catch OX OpenFile signal, which will tell us the clicked file
780         GtkosxApplication *theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
781         char *path = gtkosx_application_get_bundle_path();
782         strncpy(dataDir, path, MSG_SIZ);
783         snprintf(masterSettings, MSG_SIZ, "%s/Contents/Resources/etc/xboard.conf", path);
784         g_signal_connect(theApp, "NSApplicationOpenFile", G_CALLBACK(StartNewXBoard), NULL);
785         // we must call application ready before we can get the signal,
786         // and supply a (dummy) menu bar before that, to avoid problems with dual apples in it
787         gtkosx_application_set_menu_bar(theApp, GTK_MENU_SHELL(gtk_menu_bar_new()));
788         gtkosx_application_ready(theApp);
789         suppress = (argc == 1 || argc > 1 && argv[1][00] != '-'); // OSX sends signal even if name was already argv[1]!
790         if(argc == 1) {                  // called without args: OSX open-file signal might follow
791             static char *fakeArgv[3] = {NULL, clickedFile, NULL};
792             usleep(10000);               // wait 10 msec (and hope this is long enough).
793             while(gtk_events_pending())
794                 gtk_main_iteration();    // process all events that came in upto now
795             suppress = 0;                // future open-file signals should start new instance
796             if(clickedFile[0]) {         // we were sent an open-file signal with filename!
797               fakeArgv[0] = argv[0];
798               argc = 2; argv = fakeArgv; // fake that we were called as "xboard filename"
799             }
800         }
801     }
802 #endif
803
804     if(argc > 1 && !strcmp(argv[1], "--show-config")) { // [HGM] install: called to print config info
805         typedef struct {char *name, *value; } Config;
806         static Config configList[] = {
807           { "Datadir", DATADIR },
808           { "Sysconfdir", SYSCONFDIR },
809           { NULL }
810         };
811         int i;
812
813         for(i=0; configList[i].name; i++) {
814             if(argc > 2 && strcmp(argv[2], configList[i].name)) continue;
815             if(argc > 2) printf("%s", configList[i].value);
816             else printf("%-12s: %s\n", configList[i].name, configList[i].value);
817         }
818         exit(0);
819     }
820
821     /* set up keyboard accelerators group */
822     GtkAccelerators = gtk_accel_group_new();
823
824     programName = strrchr(argv[0], '/');
825     if (programName == NULL)
826       programName = argv[0];
827     else
828       programName++;
829
830 #ifdef ENABLE_NLS
831 //    if (appData.debugMode) {
832 //      fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
833 //    }
834
835     bindtextdomain(PACKAGE, LOCALEDIR);
836     bind_textdomain_codeset(PACKAGE, "UTF-8"); // needed when creating markup for the clocks
837     textdomain(PACKAGE);
838 #endif
839
840     appData.boardSize = "";
841     InitAppData(ConvertToLine(argc, argv));
842     p = getenv("HOME");
843     if (p == NULL) p = "/tmp";
844     i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
845     gameCopyFilename = (char*) malloc(i);
846     gamePasteFilename = (char*) malloc(i);
847     snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
848     snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
849
850     { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
851         static char buf[MSG_SIZ];
852         snprintf(buf, MSG_SIZ, appData.sysOpen, DATADIR);
853         ASSIGN(appData.sysOpen, buf); // expand %s in -openCommand to DATADIR (usefull for OS X configuring)
854         EscapeExpand(buf, appData.firstInitString);
855         appData.firstInitString = strdup(buf);
856         EscapeExpand(buf, appData.secondInitString);
857         appData.secondInitString = strdup(buf);
858         EscapeExpand(buf, appData.firstComputerString);
859         appData.firstComputerString = strdup(buf);
860         EscapeExpand(buf, appData.secondComputerString);
861         appData.secondComputerString = strdup(buf);
862     }
863
864     if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
865         chessDir = ".";
866     } else {
867         if (chdir(chessDir) != 0) {
868             fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
869             perror(chessDir);
870             exit(1);
871         }
872     }
873
874     if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
875         /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
876         if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL)  {
877            printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
878            exit(errno);
879         }
880         setbuf(debugFP, NULL);
881     }
882
883 #if ENABLE_NLS
884     if (appData.debugMode) {
885       fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
886     }
887 #endif
888
889     /* [HGM,HR] make sure board size is acceptable */
890     if(appData.NrFiles > BOARD_FILES ||
891        appData.NrRanks > BOARD_RANKS   )
892          DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
893
894 #if !HIGHDRAG
895     /* This feature does not work; animation needs a rewrite */
896     appData.highlightDragging = FALSE;
897 #endif
898     InitBackEnd1();
899
900         gameInfo.variant = StringToVariant(appData.variant);
901         InitPosition(FALSE);
902
903     /*
904      * determine size, based on supplied or remembered -size, or screen size
905      */
906     if (isdigit(appData.boardSize[0])) {
907         i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
908                    &lineGap, &clockFontPxlSize, &coordFontPxlSize,
909                    &fontPxlSize, &smallLayout, &tinyLayout);
910         if (i == 0) {
911             fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
912                     programName, appData.boardSize);
913             exit(2);
914         }
915         if (i < 7) {
916             /* Find some defaults; use the nearest known size */
917             SizeDefaults *szd, *nearest;
918             int distance = 99999;
919             nearest = szd = sizeDefaults;
920             while (szd->name != NULL) {
921                 if (abs(szd->squareSize - squareSize) < distance) {
922                     nearest = szd;
923                     distance = abs(szd->squareSize - squareSize);
924                     if (distance == 0) break;
925                 }
926                 szd++;
927             }
928             if (i < 2) lineGap = nearest->lineGap;
929             if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
930             if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
931             if (i < 5) fontPxlSize = nearest->fontPxlSize;
932             if (i < 6) smallLayout = nearest->smallLayout;
933             if (i < 7) tinyLayout = nearest->tinyLayout;
934         }
935     } else {
936         SizeDefaults *szd = sizeDefaults;
937         if (*appData.boardSize == NULLCHAR) {
938             GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow));
939             guint screenwidth = gdk_screen_get_width(screen);
940             guint screenheight = gdk_screen_get_height(screen);
941             while (screenwidth < szd->minScreenSize ||
942                    screenheight < szd->minScreenSize) {
943               szd++;
944             }
945             if (szd->name == NULL) szd--;
946             appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
947         } else {
948             while (szd->name != NULL &&
949                    StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
950             if (szd->name == NULL) {
951                 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
952                         programName, appData.boardSize);
953                 exit(2);
954             }
955         }
956         squareSize = szd->squareSize;
957         lineGap = szd->lineGap;
958         clockFontPxlSize = szd->clockFontPxlSize;
959         coordFontPxlSize = szd->coordFontPxlSize;
960         fontPxlSize = szd->fontPxlSize;
961         smallLayout = szd->smallLayout;
962         tinyLayout = szd->tinyLayout;
963         // [HGM] font: use defaults from settings file if available and not overruled
964     }
965
966     defaultLineGap = lineGap;
967     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
968
969     /* [HR] height treated separately (hacked) */
970     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
971 //    boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
972
973     /*
974      * Determine what fonts to use.
975      */
976 #ifdef TODO_GTK
977     InitializeFonts(clockFontPxlSize, coordFontPxlSize, fontPxlSize);
978 #endif
979
980     /*
981      * Detect if there are not enough colors available and adapt.
982      */
983 #ifdef TODO_GTK
984     if (DefaultDepth(xDisplay, xScreen) <= 2) {
985       appData.monoMode = True;
986     }
987 #endif
988
989     forceMono = MakeColors();
990
991     if (forceMono) {
992       fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
993               programName);
994         appData.monoMode = True;
995     }
996
997     ParseIcsTextColors();
998
999     /*
1000      * widget hierarchy
1001      */
1002     if (tinyLayout) {
1003         layoutName = "tinyLayout";
1004     } else if (smallLayout) {
1005         layoutName = "smallLayout";
1006     } else {
1007         layoutName = "normalLayout";
1008     }
1009
1010     wpMain.width = -1; // prevent popup sizes window
1011     optList = BoardPopUp(squareSize, lineGap, (void*)
1012 #ifdef TODO_GTK
1013 #if ENABLE_NLS
1014                                                 &clockFontSet);
1015 #else
1016                                                 clockFontStruct);
1017 #endif
1018 #else
1019 0);
1020 #endif
1021     InitDrawingHandle(optList + W_BOARD);
1022     shellWidget      = shells[BoardWindow];
1023     currBoard        = &optList[W_BOARD];
1024     boardWidget      = optList[W_BOARD].handle;
1025     menuBarWidget    = optList[W_MENU].handle;
1026     dropMenu         = optList[W_DROP].handle;
1027     titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1028 #ifdef TODO_GTK
1029     formWidget  = XtParent(boardWidget);
1030     XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1031     XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1032     XtGetValues(optList[W_WHITE].handle, args, 2);
1033     if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1034       XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1035       XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1036       XtGetValues(optList[W_PAUSE].handle, args, 2);
1037     }
1038 #endif
1039
1040     // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1041     //       not need to go into InitDrawingSizes().
1042
1043     InitMenuMarkers();
1044
1045     // add accelerators to main shell
1046     gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1047
1048     /*
1049      * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1050      */
1051     WhiteIcon  = gdk_pixbuf_new_from_file(SVGDIR "/icon_white.svg", NULL);
1052     BlackIcon  = gdk_pixbuf_new_from_file(SVGDIR "/icon_black.svg", NULL);
1053     mainwindowIcon = WhiteIcon;
1054     gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1055
1056
1057     /*
1058      * Create a cursor for the board widget.
1059      */
1060 #ifdef TODO_GTK
1061     window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1062     XChangeWindowAttributes(xDisplay, xBoardWindow,
1063                             CWCursor, &window_attributes);
1064 #endif
1065
1066     /*
1067      * Inhibit shell resizing.
1068      */
1069 #ifdef TODO_GTK
1070     shellArgs[0].value = (XtArgVal) &w;
1071     shellArgs[1].value = (XtArgVal) &h;
1072     XtGetValues(shellWidget, shellArgs, 2);
1073     shellArgs[4].value = shellArgs[2].value = w;
1074     shellArgs[5].value = shellArgs[3].value = h;
1075 //    XtSetValues(shellWidget, &shellArgs[2], 4);
1076 #endif
1077     {
1078         // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1079         // It wil only become known asynchronously, when we first write a string into it.
1080         // This will then change the clock widget height, which triggers resizing the top-level window
1081         // and a configure event. Only then can we know the total height of the top-level window,
1082         // and calculate the height we need. The clockKludge flag suppresses all resizing until
1083         // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1084         int hc;
1085         GtkAllocation a;
1086         gtk_widget_get_allocation(shells[BoardWindow], &a);
1087         w = a.width; h = a.height;
1088         gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1089         clockKludge = hc = a.height;
1090         gtk_widget_get_allocation(boardWidget, &a);
1091         marginW =  w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1092         marginH =  h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1093     }
1094
1095     CreateAnyPieces();
1096     CreateGrid();
1097
1098     if(appData.logoSize)
1099     {   // locate and read user logo
1100         char buf[MSG_SIZ];
1101         snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1102         ASSIGN(userLogo, buf);
1103     }
1104
1105     if (appData.animate || appData.animateDragging)
1106       CreateAnimVars();
1107
1108     g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1109     g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1110
1111     /* [AS] Restore layout */
1112     if( wpMoveHistory.visible ) {
1113       HistoryPopUp();
1114     }
1115
1116     if( wpEvalGraph.visible )
1117       {
1118         EvalGraphPopUp();
1119       };
1120
1121     if( wpEngineOutput.visible ) {
1122       EngineOutputPopUp();
1123     }
1124
1125     if( wpConsole.visible && appData.icsActive ) {
1126       ChatProc();
1127       BoardToTop();
1128     }
1129
1130     InitBackEnd2();
1131
1132     if (errorExitStatus == -1) {
1133         if (appData.icsActive) {
1134             /* We now wait until we see "login:" from the ICS before
1135                sending the logon script (problems with timestamp otherwise) */
1136             /*ICSInitScript();*/
1137             if (appData.icsInputBox) ICSInputBoxPopUp();
1138         }
1139
1140     #ifdef SIGWINCH
1141     signal(SIGWINCH, TermSizeSigHandler);
1142     #endif
1143         signal(SIGINT, IntSigHandler);
1144         signal(SIGTERM, IntSigHandler);
1145         if (*appData.cmailGameName != NULLCHAR) {
1146             signal(SIGUSR1, CmailSigHandler);
1147         }
1148     }
1149
1150     gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1151     InitPosition(TRUE);
1152     UpdateLogos(TRUE);
1153 //    XtSetKeyboardFocus(shellWidget, formWidget);
1154 #ifdef TODO_GTK
1155     XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1156 #endif
1157
1158     /* check for GTK events and process them */
1159 //    gtk_main();
1160 while(1) {
1161 gtk_main_iteration();
1162 }
1163
1164     if (appData.debugMode) fclose(debugFP); // [DM] debug
1165     return 0;
1166 }
1167
1168 RETSIGTYPE
1169 TermSizeSigHandler (int sig)
1170 {
1171     update_ics_width();
1172 }
1173
1174 RETSIGTYPE
1175 IntSigHandler (int sig)
1176 {
1177     ExitEvent(sig);
1178 }
1179
1180 RETSIGTYPE
1181 CmailSigHandler (int sig)
1182 {
1183     int dummy = 0;
1184     int error;
1185
1186     signal(SIGUSR1, SIG_IGN);   /* suspend handler     */
1187
1188     /* Activate call-back function CmailSigHandlerCallBack()             */
1189     OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1190
1191     signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1192 }
1193
1194 void
1195 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1196 {
1197     BoardToTop();
1198     ReloadCmailMsgEvent(TRUE);  /* Reload cmail msg  */
1199 }
1200 /**** end signal code ****/
1201
1202
1203 #define Abs(n) ((n)<0 ? -(n) : (n))
1204
1205 #ifdef ENABLE_NLS
1206 char *
1207 InsertPxlSize (char *pattern, int targetPxlSize)
1208 {
1209     char *base_fnt_lst, strInt[12], *p, *q;
1210     int alternatives, i, len, strIntLen;
1211
1212     /*
1213      * Replace the "*" (if present) in the pixel-size slot of each
1214      * alternative with the targetPxlSize.
1215      */
1216     p = pattern;
1217     alternatives = 1;
1218     while ((p = strchr(p, ',')) != NULL) {
1219       alternatives++;
1220       p++;
1221     }
1222     snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1223     strIntLen = strlen(strInt);
1224     base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1225
1226     p = pattern;
1227     q = base_fnt_lst;
1228     while (alternatives--) {
1229       char *comma = strchr(p, ',');
1230       for (i=0; i<14; i++) {
1231         char *hyphen = strchr(p, '-');
1232         if (!hyphen) break;
1233         if (comma && hyphen > comma) break;
1234         len = hyphen + 1 - p;
1235         if (i == 7 && *p == '*' && len == 2) {
1236           p += len;
1237           memcpy(q, strInt, strIntLen);
1238           q += strIntLen;
1239           *q++ = '-';
1240         } else {
1241           memcpy(q, p, len);
1242           p += len;
1243           q += len;
1244         }
1245       }
1246       if (!comma) break;
1247       len = comma + 1 - p;
1248       memcpy(q, p, len);
1249       p += len;
1250       q += len;
1251     }
1252     strcpy(q, p);
1253
1254     return base_fnt_lst;
1255 }
1256
1257 #ifdef TODO_GTK
1258 XFontSet
1259 CreateFontSet (char *base_fnt_lst)
1260 {
1261     XFontSet fntSet;
1262     char **missing_list;
1263     int missing_count;
1264     char *def_string;
1265
1266     fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1267                             &missing_list, &missing_count, &def_string);
1268     if (appData.debugMode) {
1269       int i, count;
1270       XFontStruct **font_struct_list;
1271       char **font_name_list;
1272       fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1273       if (fntSet) {
1274         fprintf(debugFP, " got list %s, locale %s\n",
1275                 XBaseFontNameListOfFontSet(fntSet),
1276                 XLocaleOfFontSet(fntSet));
1277         count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1278         for (i = 0; i < count; i++) {
1279           fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1280         }
1281       }
1282       for (i = 0; i < missing_count; i++) {
1283         fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1284       }
1285     }
1286     if (fntSet == NULL) {
1287       fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1288       exit(2);
1289     }
1290     return fntSet;
1291 }
1292 #endif
1293 #else // not ENABLE_NLS
1294 /*
1295  * Find a font that matches "pattern" that is as close as
1296  * possible to the targetPxlSize.  Prefer fonts that are k
1297  * pixels smaller to fonts that are k pixels larger.  The
1298  * pattern must be in the X Consortium standard format,
1299  * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1300  * The return value should be freed with XtFree when no
1301  * longer needed.
1302  */
1303 char *
1304 FindFont (char *pattern, int targetPxlSize)
1305 {
1306     char **fonts, *p, *best, *scalable, *scalableTail;
1307     int i, j, nfonts, minerr, err, pxlSize;
1308
1309 #ifdef TODO_GTK
1310     fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1311     if (nfonts < 1) {
1312         fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1313                 programName, pattern);
1314         exit(2);
1315     }
1316
1317     best = fonts[0];
1318     scalable = NULL;
1319     minerr = 999999;
1320     for (i=0; i<nfonts; i++) {
1321         j = 0;
1322         p = fonts[i];
1323         if (*p != '-') continue;
1324         while (j < 7) {
1325             if (*p == NULLCHAR) break;
1326             if (*p++ == '-') j++;
1327         }
1328         if (j < 7) continue;
1329         pxlSize = atoi(p);
1330         if (pxlSize == 0) {
1331             scalable = fonts[i];
1332             scalableTail = p;
1333         } else {
1334             err = pxlSize - targetPxlSize;
1335             if (Abs(err) < Abs(minerr) ||
1336                 (minerr > 0 && err < 0 && -err == minerr)) {
1337                 best = fonts[i];
1338                 minerr = err;
1339             }
1340         }
1341     }
1342     if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1343         /* If the error is too big and there is a scalable font,
1344            use the scalable font. */
1345         int headlen = scalableTail - scalable;
1346         p = (char *) XtMalloc(strlen(scalable) + 10);
1347         while (isdigit(*scalableTail)) scalableTail++;
1348         sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1349     } else {
1350         p = (char *) XtMalloc(strlen(best) + 2);
1351         safeStrCpy(p, best, strlen(best)+1 );
1352     }
1353     if (appData.debugMode) {
1354         fprintf(debugFP, "resolved %s at pixel size %d\n  to %s\n",
1355                 pattern, targetPxlSize, p);
1356     }
1357     XFreeFontNames(fonts);
1358 #endif
1359     return p;
1360 }
1361 #endif
1362
1363 void
1364 EnableNamedMenuItem (char *menuRef, int state)
1365 {
1366     MenuItem *item = MenuNameToItem(menuRef);
1367
1368     if(item && item->handle) gtk_widget_set_sensitive(item->handle, state);
1369 }
1370
1371 void
1372 EnableButtonBar (int state)
1373 {
1374 #ifdef TODO_GTK
1375     XtSetSensitive(optList[W_BUTTON].handle, state);
1376 #endif
1377 }
1378
1379
1380 void
1381 SetMenuEnables (Enables *enab)
1382 {
1383   while (enab->name != NULL) {
1384     EnableNamedMenuItem(enab->name, enab->value);
1385     enab++;
1386   }
1387 }
1388
1389 gboolean KeyPressProc(window, eventkey, data)
1390      GtkWindow *window;
1391      GdkEventKey  *eventkey;
1392      gpointer data;
1393 {
1394
1395     MoveTypeInProc(eventkey); // pop up for typed in moves
1396
1397 #ifdef TODO_GTK
1398     /* check for other key values */
1399     switch(eventkey->keyval) {
1400         case GDK_question:
1401           AboutGameEvent();
1402           break;
1403         default:
1404           break;
1405     }
1406 #endif
1407     return False;
1408 }
1409 #ifdef TODO_GTK
1410 void
1411 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1412 {   // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1413     MenuItem *item;
1414     if(*nprms == 0) return;
1415     item = MenuNameToItem(prms[0]);
1416     if(item) ((MenuProc *) item->proc) ();
1417 }
1418 #endif
1419
1420 void
1421 SetupDropMenu ()
1422 {
1423 #ifdef TODO_GTK
1424     int i, j, count;
1425     char label[32];
1426     Arg args[16];
1427     Widget entry;
1428     char* p;
1429
1430     for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1431         entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1432         p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1433                    dmEnables[i].piece);
1434         XtSetSensitive(entry, p != NULL || !appData.testLegality
1435                        /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1436                                        && !appData.icsActive));
1437         count = 0;
1438         while (p && *p++ == dmEnables[i].piece) count++;
1439         snprintf(label, sizeof(label), "%s  %d", dmEnables[i].widget, count);
1440         j = 0;
1441         XtSetArg(args[j], XtNlabel, label); j++;
1442         XtSetValues(entry, args, j);
1443     }
1444 #endif
1445 }
1446
1447 static void
1448 do_flash_delay (unsigned long msec)
1449 {
1450     TimeDelay(msec);
1451 }
1452
1453 void
1454 FlashDelay (int flash_delay)
1455 {
1456         if(flash_delay) do_flash_delay(flash_delay);
1457 }
1458
1459 double
1460 Fraction (int x, int start, int stop)
1461 {
1462    double f = ((double) x - start)/(stop - start);
1463    if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1464    return f;
1465 }
1466
1467 static WindowPlacement wpNew;
1468
1469 void
1470 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1471 {
1472     int touch=0, fudge = 2, f = 2;
1473     GetActualPlacement(sh, wp);
1474     if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x)         < fudge) touch = 1; else // right touch
1475     if(abs(wp->x + wp->width + 2*frameX + f - wpMain.x)            < fudge) touch = 2; else // left touch
1476     if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1477     if(abs(wp->y + wp->height + frameX + frameY + f - wpMain.y)    < fudge) touch = 4;      // top touch
1478 //printf("CoDrag: touch = %d x=%d w=%d x2=%d w2=%d fx=%d\n", touch, wpMain.x, wpMain.width, wp->x, wp->width, frameX);
1479     if(!touch ) return; // only windows that touch co-move
1480     if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1481         int heightInc = wpNew.height - wpMain.height;
1482         double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1483         double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1484         wp->y += fracTop * heightInc;
1485         heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1486 #ifdef TODO_GTK
1487         if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1488 #endif
1489         wp->height += heightInc;
1490     } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1491         int widthInc = wpNew.width - wpMain.width;
1492         double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1493         double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1494         wp->y += fracLeft * widthInc;
1495         widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1496 #ifdef TODO_GTK
1497         if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1498 #endif
1499         wp->width += widthInc;
1500     }
1501     wp->x += wpNew.x - wpMain.x;
1502     wp->y += wpNew.y - wpMain.y;
1503     if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1504     if(touch == 3) wp->y += wpNew.height - wpMain.height;
1505 #ifdef TODO_GTK
1506     XtSetArg(args[j], XtNx, wp->x); j++;
1507     XtSetArg(args[j], XtNy, wp->y); j++;
1508     XtSetValues(sh, args, j);
1509 #endif
1510         gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1511 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1512         gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1513 }
1514
1515 void
1516 ReSize (WindowPlacement *wp)
1517 {
1518         GtkAllocation a;
1519         int sqx, sqy, w, h, hc, lg = lineGap;
1520         gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1521         hc = a.height; // clock height can depend on single / double line clock text!
1522         if(clockKludge == a.height) return; // wait for clock to get final size at startup
1523         if(clockKludge) { // clock height OK now; calculate desired initial board height
1524             clockKludge = 0;
1525             wp->height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1526         }
1527         if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1528         sqx = (wp->width  - lg - marginW) / BOARD_WIDTH - lg;
1529         sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1530         if(sqy < sqx) sqx = sqy;
1531         if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1532             lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1533             sqx = (wp->width  - lg - marginW) / BOARD_WIDTH - lg;
1534             sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1535             if(sqy < sqx) sqx = sqy;
1536         }
1537         if(sqx != squareSize) {
1538 //printf("new sq size %d (%dx%d)\n", sqx, wp->width, wp->height);
1539             squareSize = sqx; // adopt new square size
1540             CreatePNGPieces(); // make newly scaled pieces
1541             InitDrawingSizes(0, 0); // creates grid etc.
1542         } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1543         w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1544         h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1545         if(optList[W_BOARD].max   > w) optList[W_BOARD].max = w;
1546         if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1547 }
1548
1549 static guint delayedDragTag = 0;
1550
1551 void
1552 DragProc ()
1553 {
1554         static int busy;
1555         if(busy) return;
1556
1557         busy = 1;
1558 //      GetActualPlacement(shellWidget, &wpNew);
1559         if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1560            wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1561             busy = 0; return; // false alarm
1562         }
1563         ReSize(&wpNew);
1564         if(appData.useStickyWindows) {
1565             if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1566             if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1567             if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1568             if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1569             if(shellUp[ChatDlg]) CoDrag(shells[ChatDlg], &wpConsole);
1570         }
1571         wpMain = wpNew;
1572         DrawPosition(True, NULL);
1573         if(delayedDragTag) g_source_remove(delayedDragTag);
1574         delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1575         busy = 0;
1576 }
1577
1578 void
1579 DelayedDrag ()
1580 {
1581 //printf("old timr = %d\n", delayedDragTag);
1582     if(delayedDragTag) g_source_remove(delayedDragTag);
1583     delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1584 //printf("new timr = %d\n", delayedDragTag);
1585 }
1586
1587 static gboolean
1588 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1589 {
1590 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1591     // immediately
1592     wpNew.x = event->configure.x;
1593     wpNew.y = event->configure.y;
1594     wpNew.width  = event->configure.width;
1595     wpNew.height = event->configure.height;
1596     DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1597     return FALSE;
1598 }
1599
1600
1601
1602 /* Disable all user input other than deleting the window */
1603 static int frozen = 0;
1604
1605 void
1606 FreezeUI ()
1607 {
1608   if (frozen) return;
1609   /* Grab by a widget that doesn't accept input */
1610   gtk_grab_add(optList[W_MESSG].handle);
1611   frozen = 1;
1612 }
1613
1614 /* Undo a FreezeUI */
1615 void
1616 ThawUI ()
1617 {
1618   if (!frozen) return;
1619   gtk_grab_remove(optList[W_MESSG].handle);
1620   frozen = 0;
1621 }
1622
1623 void
1624 ModeHighlight ()
1625 {
1626     static int oldPausing = FALSE;
1627     static GameMode oldmode = (GameMode) -1;
1628     char *wname;
1629     if (!boardWidget) return;
1630
1631     if (pausing != oldPausing) {
1632         oldPausing = pausing;
1633         MarkMenuItem("Mode.Pause", pausing);
1634
1635         if (appData.showButtonBar) {
1636           /* Always toggle, don't set.  Previous code messes up when
1637              invoked while the button is pressed, as releasing it
1638              toggles the state again. */
1639             GdkColor color;
1640             gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1641             gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1642         }
1643     }
1644
1645     wname = ModeToWidgetName(oldmode);
1646     if (wname != NULL) {
1647         MarkMenuItem(wname, False);
1648     }
1649     wname = ModeToWidgetName(gameMode);
1650     if (wname != NULL) {
1651         MarkMenuItem(wname, True);
1652     }
1653     oldmode = gameMode;
1654     MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1655
1656     /* Maybe all the enables should be handled here, not just this one */
1657     EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1658
1659     DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1660 }
1661
1662
1663 /*
1664  * Button/menu procedures
1665  */
1666
1667 void CopyFileToClipboard(gchar *filename)
1668 {
1669     gchar *selection_tmp;
1670     GtkClipboard *cb;
1671
1672     // read the file
1673     FILE* f = fopen(filename, "r");
1674     long len;
1675     size_t count;
1676     if (f == NULL) return;
1677     fseek(f, 0, 2);
1678     len = ftell(f);
1679     rewind(f);
1680     selection_tmp = g_try_malloc(len + 1);
1681     if (selection_tmp == NULL) {
1682         printf("Malloc failed in CopyFileToClipboard\n");
1683         return;
1684     }
1685     count = fread(selection_tmp, 1, len, f);
1686     fclose(f);
1687     if (len != count) {
1688       g_free(selection_tmp);
1689       return;
1690     }
1691     selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1692
1693     // copy selection_tmp to clipboard
1694     GdkDisplay *gdisp = gdk_display_get_default();
1695     if (!gdisp) {
1696         g_free(selection_tmp);
1697         return;
1698     }
1699     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1700     gtk_clipboard_set_text(cb, selection_tmp, -1);
1701     g_free(selection_tmp);
1702 }
1703
1704 void
1705 CopySomething (char *src)
1706 {
1707     GdkDisplay *gdisp = gdk_display_get_default();
1708     GtkClipboard *cb;
1709     if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1710     if (gdisp == NULL) return;
1711     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1712     gtk_clipboard_set_text(cb, src, -1);
1713 }
1714
1715 void
1716 PastePositionProc ()
1717 {
1718     GdkDisplay *gdisp = gdk_display_get_default();
1719     GtkClipboard *cb;
1720     gchar *fenstr;
1721
1722     if (gdisp == NULL) return;
1723     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1724     fenstr = gtk_clipboard_wait_for_text(cb);
1725     if (fenstr==NULL) return; // nothing had been selected to copy
1726     EditPositionPasteFEN(fenstr);
1727     return;
1728 }
1729
1730 void
1731 PasteGameProc ()
1732 {
1733     gchar *text=NULL;
1734     GtkClipboard *cb;
1735     guint len=0;
1736     FILE* f;
1737
1738     // get game from clipboard
1739     GdkDisplay *gdisp = gdk_display_get_default();
1740     if (gdisp == NULL) return;
1741     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1742     text = gtk_clipboard_wait_for_text(cb);
1743     if (text == NULL) return; // nothing to paste
1744     len = strlen(text);
1745
1746     // write to temp file
1747     if (text == NULL || len == 0) {
1748       return; //nothing to paste
1749     }
1750     f = fopen(gamePasteFilename, "w");
1751     if (f == NULL) {
1752       DisplayError(_("Can't open temp file"), errno);
1753       return;
1754     }
1755     fwrite(text, 1, len, f);
1756     fclose(f);
1757
1758     // load from file
1759     LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1760     return;
1761 }
1762
1763
1764 #ifdef TODO_GTK
1765 void
1766 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1767 {
1768     QuitProc();
1769 }
1770 #endif
1771
1772 void MoveTypeInProc(eventkey)
1773     GdkEventKey  *eventkey;
1774 {
1775     char buf[10];
1776
1777     // ingnore if ctrl, alt, or meta is pressed
1778     if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1779         return;
1780     }
1781
1782     buf[0]=eventkey->keyval;
1783     buf[1]='\0';
1784     if (eventkey->keyval > 32 && eventkey->keyval < 256)
1785         BoxAutoPopUp (buf);
1786 }
1787
1788 #ifdef TODO_GTK
1789 void
1790 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1791 {
1792         if (!TempBackwardActive) {
1793                 TempBackwardActive = True;
1794                 BackwardEvent();
1795         }
1796 }
1797
1798 void
1799 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1800 {
1801         /* Check to see if triggered by a key release event for a repeating key.
1802          * If so the next queued event will be a key press of the same key at the same time */
1803         if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1804                 XEvent next;
1805                 XPeekEvent(xDisplay, &next);
1806                 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1807                         next.xkey.keycode == event->xkey.keycode)
1808                                 return;
1809         }
1810     ForwardEvent();
1811         TempBackwardActive = False;
1812 }
1813 #endif
1814
1815 void
1816 ManProc ()
1817 {   // called from menu
1818 #ifdef __APPLE__
1819     char buf[MSG_SIZ];
1820     snprintf(buf, MSG_SIZ, "%s ./man.command", appData.sysOpen);
1821     system(buf);
1822 #else
1823     system("xterm -e man xboard &");
1824 #endif
1825 }
1826
1827 void
1828 SetWindowTitle (char *text, char *title, char *icon)
1829 {
1830 #ifdef TODO_GTK
1831     Arg args[16];
1832     int i;
1833     if (appData.titleInWindow) {
1834         i = 0;
1835         XtSetArg(args[i], XtNlabel, text);   i++;
1836         XtSetValues(titleWidget, args, i);
1837     }
1838     i = 0;
1839     XtSetArg(args[i], XtNiconName, (XtArgVal) icon);    i++;
1840     XtSetArg(args[i], XtNtitle, (XtArgVal) title);      i++;
1841     XtSetValues(shellWidget, args, i);
1842     XSync(xDisplay, False);
1843 #endif
1844     if (appData.titleInWindow) {
1845         SetWidgetLabel(titleWidget, text);
1846     }
1847     gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1848 }
1849
1850
1851 void
1852 DisplayIcsInteractionTitle (String message)
1853 {
1854 #ifdef TODO_GTK
1855   if (oldICSInteractionTitle == NULL) {
1856     /* Magic to find the old window title, adapted from vim */
1857     char *wina = getenv("WINDOWID");
1858     if (wina != NULL) {
1859       Window win = (Window) atoi(wina);
1860       Window root, parent, *children;
1861       unsigned int nchildren;
1862       int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
1863       for (;;) {
1864         if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
1865         if (!XQueryTree(xDisplay, win, &root, &parent,
1866                         &children, &nchildren)) break;
1867         if (children) XFree((void *)children);
1868         if (parent == root || parent == 0) break;
1869         win = parent;
1870       }
1871       XSetErrorHandler(oldHandler);
1872     }
1873     if (oldICSInteractionTitle == NULL) {
1874       oldICSInteractionTitle = "xterm";
1875     }
1876   }
1877   printf("\033]0;%s\007", message);
1878   fflush(stdout);
1879 #endif
1880 }
1881
1882
1883 void
1884 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
1885 {
1886     GtkWidget *w = (GtkWidget *) opt->handle;
1887     GdkColor col;
1888     char *markup;
1889     char bgcolor[10];
1890     char fgcolor[10];
1891
1892     if (highlight) {
1893         strcpy(bgcolor, "black");
1894         strcpy(fgcolor, "white");
1895     } else {
1896         strcpy(bgcolor, "white");
1897         strcpy(fgcolor, "black");
1898     }
1899     if (timer > 0 &&
1900         appData.lowTimeWarning &&
1901         (timer / 1000) < appData.icsAlarmTime) {
1902         strcpy(fgcolor, appData.lowTimeWarningColor);
1903     }
1904
1905     gdk_color_parse( bgcolor, &col );
1906     gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
1907
1908     if (appData.clockMode) {
1909         markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
1910                                          bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
1911     } else {
1912         markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s  </span>",
1913                                          bgcolor, fgcolor, color);
1914     }
1915     gtk_label_set_markup(GTK_LABEL(w), markup);
1916     g_free(markup);
1917 }
1918
1919 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
1920
1921 void
1922 SetClockIcon (int color)
1923 {
1924     GdkPixbuf *pm = *clockIcons[color];
1925     if (mainwindowIcon != pm) {
1926         mainwindowIcon = pm;
1927         gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1928     }
1929 }
1930
1931 #define INPUT_SOURCE_BUF_SIZE 8192
1932
1933 typedef struct {
1934     CPKind kind;
1935     int fd;
1936     int lineByLine;
1937     char *unused;
1938     InputCallback func;
1939     guint sid;
1940     char buf[INPUT_SOURCE_BUF_SIZE];
1941     VOIDSTAR closure;
1942 } InputSource;
1943
1944 gboolean
1945 DoInputCallback(io, cond, data)
1946      GIOChannel  *io;
1947      GIOCondition cond;
1948      gpointer    *data;
1949 {
1950   /* read input from one of the input source (for example a chess program, ICS, etc).
1951    * and call a function that will handle the input
1952    */
1953
1954     int count;
1955     int error;
1956     char *p, *q;
1957
1958     /* All information (callback function, file descriptor, etc) is
1959      * saved in an InputSource structure
1960      */
1961     InputSource *is = (InputSource *) data;
1962
1963     if (is->lineByLine) {
1964         count = read(is->fd, is->unused,
1965                      INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
1966         if (count <= 0) {
1967             (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
1968             return True;
1969         }
1970         is->unused += count;
1971         p = is->buf;
1972         /* break input into lines and call the callback function on each
1973          * line
1974          */
1975         while (p < is->unused) {
1976             q = memchr(p, '\n', is->unused - p);
1977             if (q == NULL) break;
1978             q++;
1979             (is->func)(is, is->closure, p, q - p, 0);
1980             p = q;
1981         }
1982         /* remember not yet used part of the buffer */
1983         q = is->buf;
1984         while (p < is->unused) {
1985             *q++ = *p++;
1986         }
1987         is->unused = q;
1988     } else {
1989       /* read maximum length of input buffer and send the whole buffer
1990        * to the callback function
1991        */
1992         count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
1993         if (count == -1)
1994           error = errno;
1995         else
1996           error = 0;
1997         (is->func)(is, is->closure, is->buf, count, error);
1998     }
1999     return True; // Must return true or the watch will be removed
2000 }
2001
2002 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2003      ProcRef pr;
2004      int lineByLine;
2005      InputCallback func;
2006      VOIDSTAR closure;
2007 {
2008     InputSource *is;
2009     GIOChannel *channel;
2010     ChildProc *cp = (ChildProc *) pr;
2011
2012     is = (InputSource *) calloc(1, sizeof(InputSource));
2013     is->lineByLine = lineByLine;
2014     is->func = func;
2015     if (pr == NoProc) {
2016         is->kind = CPReal;
2017         is->fd = fileno(stdin);
2018     } else {
2019         is->kind = cp->kind;
2020         is->fd = cp->fdFrom;
2021     }
2022     if (lineByLine)
2023       is->unused = is->buf;
2024     else
2025       is->unused = NULL;
2026
2027    /* GTK-TODO: will this work on windows?*/
2028
2029     channel = g_io_channel_unix_new(is->fd);
2030     g_io_channel_set_close_on_unref (channel, TRUE);
2031     is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2032
2033     is->closure = closure;
2034     return (InputSourceRef) is;
2035 }
2036
2037
2038 void
2039 RemoveInputSource(isr)
2040      InputSourceRef isr;
2041 {
2042     InputSource *is = (InputSource *) isr;
2043
2044     if (is->sid == 0) return;
2045     g_source_remove(is->sid);
2046     is->sid = 0;
2047     return;
2048 }
2049
2050 #ifndef HAVE_USLEEP
2051
2052 static Boolean frameWaiting;
2053
2054 static RETSIGTYPE
2055 FrameAlarm (int sig)
2056 {
2057   frameWaiting = False;
2058   /* In case System-V style signals.  Needed?? */
2059   signal(SIGALRM, FrameAlarm);
2060 }
2061
2062 void
2063 FrameDelay (int time)
2064 {
2065   struct itimerval delay;
2066
2067   if (time > 0) {
2068     frameWaiting = True;
2069     signal(SIGALRM, FrameAlarm);
2070     delay.it_interval.tv_sec =
2071       delay.it_value.tv_sec = time / 1000;
2072     delay.it_interval.tv_usec =
2073       delay.it_value.tv_usec = (time % 1000) * 1000;
2074     setitimer(ITIMER_REAL, &delay, NULL);
2075     while (frameWaiting) pause();
2076     delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2077     delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2078     setitimer(ITIMER_REAL, &delay, NULL);
2079   }
2080 }
2081
2082 #else
2083
2084 void
2085 FrameDelay (int time)
2086 {
2087 #ifdef TODO_GTK
2088   XSync(xDisplay, False);
2089 #endif
2090 //  gtk_main_iteration_do(False);
2091
2092   if (time > 0)
2093     usleep(time * 1000);
2094 }
2095
2096 #endif
2097
2098 static void
2099 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2100 {
2101     char buf[MSG_SIZ], *logoName = buf;
2102     if(appData.logo[n][0]) {
2103         logoName = appData.logo[n];
2104     } else if(appData.autoLogo) {
2105         if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2106             sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2107         } else if(appData.directory[n] && appData.directory[n][0]) {
2108             sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2109         }
2110     }
2111     if(logoName[0])
2112         { ASSIGN(cps->programLogo, logoName); }
2113 }
2114
2115 void
2116 UpdateLogos (int displ)
2117 {
2118     if(optList[W_WHITE-1].handle == NULL) return;
2119     LoadLogo(&first, 0, 0);
2120     LoadLogo(&second, 1, appData.icsActive);
2121     if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2122     return;
2123 }
2124
2125 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2126      char *label;
2127      char *def;
2128      char *filter;
2129      FileProc proc;
2130      char *openMode;
2131      Boolean pathFlag;
2132      char **name;
2133      FILE **fp;
2134 {
2135   GtkWidget     *dialog;
2136   GtkFileFilter *gtkfilter;
2137   GtkFileFilter *gtkfilter_all;
2138   char space[]     = " ";
2139   char fileext[10] = "";
2140   char *result     = NULL;
2141   char *cp;
2142
2143   /* make a copy of the filter string, so that strtok can work with it*/
2144   cp = strdup(filter);
2145
2146   /* add filters for file extensions */
2147   gtkfilter     = gtk_file_filter_new();
2148   gtkfilter_all = gtk_file_filter_new();
2149
2150   /* one filter to show everything */
2151   gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2152   gtk_file_filter_set_name   (gtkfilter_all, "All Files");
2153
2154   /* add filter if present */
2155   result = strtok(cp, space);
2156   while( result != NULL  ) {
2157     snprintf(fileext,10,"*%s",result);
2158     result = strtok( NULL, space );
2159     gtk_file_filter_add_pattern(gtkfilter, fileext);
2160   };
2161
2162   /* second filter to only show what's useful */
2163   gtk_file_filter_set_name (gtkfilter,filter);
2164
2165   if (openMode[0] == 'r')
2166     {
2167       dialog = gtk_file_chooser_dialog_new (label,
2168                                             NULL,
2169                                             GTK_FILE_CHOOSER_ACTION_OPEN,
2170                                             GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2171                                             GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2172                                             NULL);
2173     }
2174   else
2175     {
2176       dialog = gtk_file_chooser_dialog_new (label,
2177                                             NULL,
2178                                             GTK_FILE_CHOOSER_ACTION_SAVE,
2179                                             GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2180                                             GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2181                                             NULL);
2182       /* add filename suggestions */
2183       if (strlen(def) > 0 )
2184         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2185
2186       //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2187     }
2188
2189   /* add filters */
2190   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2191   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2192   /* activate filter */
2193   gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2194
2195   if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2196     {
2197       char *filename;
2198       FILE *f;
2199
2200       filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2201
2202       //see loadgamepopup
2203       f = fopen(filename, openMode);
2204       if (f == NULL)
2205         {
2206           DisplayError(_("Failed to open file"), errno);
2207         }
2208       else
2209         {
2210           /* TODO add indec */
2211             *fp = f;
2212             ASSIGN(*name, filename);
2213             ScheduleDelayedEvent(DelayedLoad, 50);
2214         }
2215       g_free (filename);
2216     };
2217
2218   gtk_widget_destroy (dialog);
2219   ModeHighlight();
2220
2221   free(cp);
2222   return;
2223
2224 }