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