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