Fix castling rights on using -lgf
[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 RETSIGTYPE
1195 TermSizeSigHandler (int sig)
1196 {
1197     update_ics_width();
1198 }
1199
1200 RETSIGTYPE
1201 IntSigHandler (int sig)
1202 {
1203     ExitEvent(sig);
1204 }
1205
1206 RETSIGTYPE
1207 CmailSigHandler (int sig)
1208 {
1209     int dummy = 0;
1210     int error;
1211
1212     signal(SIGUSR1, SIG_IGN);   /* suspend handler     */
1213
1214     /* Activate call-back function CmailSigHandlerCallBack()             */
1215     OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1216
1217     signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1218 }
1219
1220 void
1221 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1222 {
1223     BoardToTop();
1224     ReloadCmailMsgEvent(TRUE);  /* Reload cmail msg  */
1225 }
1226 /**** end signal code ****/
1227
1228
1229 #define Abs(n) ((n)<0 ? -(n) : (n))
1230
1231 char *
1232 InsertPxlSize (char *pattern, int targetPxlSize)
1233 {
1234     char buf[MSG_SIZ];
1235     snprintf(buf, MSG_SIZ, pattern, targetPxlSize); // pattern is something like "Sans Bold %d"
1236     return strdup(buf);
1237 }
1238
1239 #ifdef ENABLE_NLS
1240 #ifdef TODO_GTK
1241 char *
1242 InsertPxlSize (char *pattern, int targetPxlSize)
1243 {
1244     char *base_fnt_lst, strInt[12], *p, *q;
1245     int alternatives, i, len, strIntLen;
1246
1247     /*
1248      * Replace the "*" (if present) in the pixel-size slot of each
1249      * alternative with the targetPxlSize.
1250      */
1251     p = pattern;
1252     alternatives = 1;
1253     while ((p = strchr(p, ',')) != NULL) {
1254       alternatives++;
1255       p++;
1256     }
1257     snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1258     strIntLen = strlen(strInt);
1259     base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1260
1261     p = pattern;
1262     q = base_fnt_lst;
1263     while (alternatives--) {
1264       char *comma = strchr(p, ',');
1265       for (i=0; i<14; i++) {
1266         char *hyphen = strchr(p, '-');
1267         if (!hyphen) break;
1268         if (comma && hyphen > comma) break;
1269         len = hyphen + 1 - p;
1270         if (i == 7 && *p == '*' && len == 2) {
1271           p += len;
1272           memcpy(q, strInt, strIntLen);
1273           q += strIntLen;
1274           *q++ = '-';
1275         } else {
1276           memcpy(q, p, len);
1277           p += len;
1278           q += len;
1279         }
1280       }
1281       if (!comma) break;
1282       len = comma + 1 - p;
1283       memcpy(q, p, len);
1284       p += len;
1285       q += len;
1286     }
1287     strcpy(q, p);
1288
1289     return base_fnt_lst;
1290 }
1291 #endif
1292
1293 #ifdef TODO_GTK
1294 XFontSet
1295 CreateFontSet (char *base_fnt_lst)
1296 {
1297     XFontSet fntSet;
1298     char **missing_list;
1299     int missing_count;
1300     char *def_string;
1301
1302     fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1303                             &missing_list, &missing_count, &def_string);
1304     if (appData.debugMode) {
1305       int i, count;
1306       XFontStruct **font_struct_list;
1307       char **font_name_list;
1308       fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1309       if (fntSet) {
1310         fprintf(debugFP, " got list %s, locale %s\n",
1311                 XBaseFontNameListOfFontSet(fntSet),
1312                 XLocaleOfFontSet(fntSet));
1313         count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1314         for (i = 0; i < count; i++) {
1315           fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1316         }
1317       }
1318       for (i = 0; i < missing_count; i++) {
1319         fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1320       }
1321     }
1322     if (fntSet == NULL) {
1323       fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1324       exit(2);
1325     }
1326     return fntSet;
1327 }
1328 #endif
1329 #else // not ENABLE_NLS
1330 /*
1331  * Find a font that matches "pattern" that is as close as
1332  * possible to the targetPxlSize.  Prefer fonts that are k
1333  * pixels smaller to fonts that are k pixels larger.  The
1334  * pattern must be in the X Consortium standard format,
1335  * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1336  * The return value should be freed with XtFree when no
1337  * longer needed.
1338  */
1339 #ifdef TODO_GTK
1340 char *
1341 FindFont (char *pattern, int targetPxlSize)
1342 {
1343     char **fonts, *p, *best, *scalable, *scalableTail;
1344     int i, j, nfonts, minerr, err, pxlSize;
1345
1346     fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1347     if (nfonts < 1) {
1348         fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1349                 programName, pattern);
1350         exit(2);
1351     }
1352
1353     best = fonts[0];
1354     scalable = NULL;
1355     minerr = 999999;
1356     for (i=0; i<nfonts; i++) {
1357         j = 0;
1358         p = fonts[i];
1359         if (*p != '-') continue;
1360         while (j < 7) {
1361             if (*p == NULLCHAR) break;
1362             if (*p++ == '-') j++;
1363         }
1364         if (j < 7) continue;
1365         pxlSize = atoi(p);
1366         if (pxlSize == 0) {
1367             scalable = fonts[i];
1368             scalableTail = p;
1369         } else {
1370             err = pxlSize - targetPxlSize;
1371             if (Abs(err) < Abs(minerr) ||
1372                 (minerr > 0 && err < 0 && -err == minerr)) {
1373                 best = fonts[i];
1374                 minerr = err;
1375             }
1376         }
1377     }
1378     if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1379         /* If the error is too big and there is a scalable font,
1380            use the scalable font. */
1381         int headlen = scalableTail - scalable;
1382         p = (char *) XtMalloc(strlen(scalable) + 10);
1383         while (isdigit(*scalableTail)) scalableTail++;
1384         sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1385     } else {
1386         p = (char *) XtMalloc(strlen(best) + 2);
1387         safeStrCpy(p, best, strlen(best)+1 );
1388     }
1389     if (appData.debugMode) {
1390         fprintf(debugFP, "resolved %s at pixel size %d\n  to %s\n",
1391                 pattern, targetPxlSize, p);
1392     }
1393     XFreeFontNames(fonts);
1394     return p;
1395 }
1396 #endif
1397 #endif
1398
1399 void
1400 EnableNamedMenuItem (char *menuRef, int state)
1401 {
1402     MenuItem *item = MenuNameToItem(menuRef);
1403
1404     if(item && item->handle) gtk_widget_set_sensitive(item->handle, state);
1405 }
1406
1407 void
1408 EnableButtonBar (int state)
1409 {
1410 #ifdef TODO_GTK
1411     XtSetSensitive(optList[W_BUTTON].handle, state);
1412 #endif
1413 }
1414
1415
1416 void
1417 SetMenuEnables (Enables *enab)
1418 {
1419   while (enab->name != NULL) {
1420     EnableNamedMenuItem(enab->name, enab->value);
1421     enab++;
1422   }
1423 }
1424
1425 gboolean KeyPressProc(window, eventkey, data)
1426      GtkWindow *window;
1427      GdkEventKey  *eventkey;
1428      gpointer data;
1429 {
1430
1431     MoveTypeInProc(eventkey); // pop up for typed in moves
1432
1433 #ifdef TODO_GTK
1434     /* check for other key values */
1435     switch(eventkey->keyval) {
1436         case GDK_question:
1437           AboutGameEvent();
1438           break;
1439         default:
1440           break;
1441     }
1442 #endif
1443     return False;
1444 }
1445 #ifdef TODO_GTK
1446 void
1447 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1448 {   // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1449     MenuItem *item;
1450     if(*nprms == 0) return;
1451     item = MenuNameToItem(prms[0]);
1452     if(item) ((MenuProc *) item->proc) ();
1453 }
1454 #endif
1455
1456 void
1457 SetupDropMenu ()
1458 {
1459 #ifdef TODO_GTK
1460     int i, j, count;
1461     char label[32];
1462     Arg args[16];
1463     Widget entry;
1464     char* p;
1465
1466     for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1467         entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1468         p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1469                    dmEnables[i].piece);
1470         XtSetSensitive(entry, p != NULL || !appData.testLegality
1471                        /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1472                                        && !appData.icsActive));
1473         count = 0;
1474         while (p && *p++ == dmEnables[i].piece) count++;
1475         snprintf(label, sizeof(label), "%s  %d", dmEnables[i].widget, count);
1476         j = 0;
1477         XtSetArg(args[j], XtNlabel, label); j++;
1478         XtSetValues(entry, args, j);
1479     }
1480 #endif
1481 }
1482
1483 static void
1484 do_flash_delay (unsigned long msec)
1485 {
1486     TimeDelay(msec);
1487 }
1488
1489 void
1490 FlashDelay (int flash_delay)
1491 {
1492         if(flash_delay) do_flash_delay(flash_delay);
1493 }
1494
1495 double
1496 Fraction (int x, int start, int stop)
1497 {
1498    double f = ((double) x - start)/(stop - start);
1499    if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1500    return f;
1501 }
1502
1503 static WindowPlacement wpNew;
1504
1505 void
1506 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1507 {
1508     int touch=0, fudge = 2, f = 2;
1509     GetActualPlacement(sh, wp);
1510     if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x)         < fudge) touch = 1; else // right touch
1511     if(abs(wp->x + wp->width + 2*frameX + f - wpMain.x)            < fudge) touch = 2; else // left touch
1512     if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1513     if(abs(wp->y + wp->height + frameX + frameY + f - wpMain.y)    < fudge) touch = 4;      // top touch
1514 //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);
1515     if(!touch ) return; // only windows that touch co-move
1516     if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1517         int heightInc = wpNew.height - wpMain.height;
1518         double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1519         double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1520         wp->y += fracTop * heightInc;
1521         heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1522 #ifdef TODO_GTK
1523         if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1524 #endif
1525         wp->height += heightInc;
1526     } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1527         int widthInc = wpNew.width - wpMain.width;
1528         double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1529         double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1530         wp->y += fracLeft * widthInc;
1531         widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1532 #ifdef TODO_GTK
1533         if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1534 #endif
1535         wp->width += widthInc;
1536     }
1537     wp->x += wpNew.x - wpMain.x;
1538     wp->y += wpNew.y - wpMain.y;
1539     if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1540     if(touch == 3) wp->y += wpNew.height - wpMain.height;
1541 #ifdef TODO_GTK
1542     XtSetArg(args[j], XtNx, wp->x); j++;
1543     XtSetArg(args[j], XtNy, wp->y); j++;
1544     XtSetValues(sh, args, j);
1545 #endif
1546         gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1547 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1548         gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1549 }
1550
1551 void
1552 ReSize (WindowPlacement *wp)
1553 {
1554         GtkAllocation a;
1555         int sqx, sqy, w, h, hc, lg = lineGap;
1556         gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1557         hc = a.height; // clock height can depend on single / double line clock text!
1558         if(clockKludge && hc != clockKludge) wp->height += hc - clockKludge, clockKludge = 0;
1559         wpMain.height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1560         if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1561         sqx = (wp->width  - lg - marginW) / BOARD_WIDTH - lg;
1562         sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1563         if(sqy < sqx) sqx = sqy;
1564         if(sqx < 20) return;
1565         if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1566             lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
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         }
1571         if(sqx != squareSize) {
1572             squareSize = sqx; // adopt new square size
1573             CreatePNGPieces(); // make newly scaled pieces
1574             InitDrawingSizes(0, 0); // creates grid etc.
1575         } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1576         w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1577         h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1578         if(optList[W_BOARD].max   > w) optList[W_BOARD].max = w;
1579         if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1580 }
1581
1582 static guint delayedDragTag = 0;
1583
1584 void
1585 DragProc ()
1586 {
1587         static int busy;
1588         if(busy) return;
1589
1590         busy = 1;
1591 //      GetActualPlacement(shellWidget, &wpNew);
1592         if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1593            wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1594             busy = 0; return; // false alarm
1595         }
1596         ReSize(&wpNew);
1597         if(appData.useStickyWindows) {
1598             if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1599             if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1600             if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1601             if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1602             if(shellUp[ChatDlg]) CoDrag(shells[ChatDlg], &wpConsole);
1603         }
1604         wpMain = wpNew;
1605         DrawPosition(True, NULL);
1606         if(delayedDragTag) g_source_remove(delayedDragTag);
1607         delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1608         busy = 0;
1609 }
1610
1611 void
1612 DelayedDrag ()
1613 {
1614 //printf("old timr = %d\n", delayedDragTag);
1615     if(delayedDragTag) g_source_remove(delayedDragTag);
1616     delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1617 //printf("new timr = %d\n", delayedDragTag);
1618 }
1619
1620 static gboolean
1621 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1622 {
1623 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1624     // immediately
1625     wpNew.x = event->configure.x;
1626     wpNew.y = event->configure.y;
1627     wpNew.width  = event->configure.width;
1628     wpNew.height = event->configure.height;
1629     DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1630     return FALSE;
1631 }
1632
1633
1634
1635 /* Disable all user input other than deleting the window */
1636 static int frozen = 0;
1637
1638 void
1639 FreezeUI ()
1640 {
1641   if (frozen) return;
1642   /* Grab by a widget that doesn't accept input */
1643   gtk_grab_add(optList[W_MESSG].handle);
1644   frozen = 1;
1645 }
1646
1647 /* Undo a FreezeUI */
1648 void
1649 ThawUI ()
1650 {
1651   if (!frozen) return;
1652   gtk_grab_remove(optList[W_MESSG].handle);
1653   frozen = 0;
1654 }
1655
1656 void
1657 ModeHighlight ()
1658 {
1659     static int oldPausing = FALSE;
1660     static GameMode oldmode = (GameMode) -1;
1661     char *wname;
1662     if (!boardWidget) return;
1663
1664     if (pausing != oldPausing) {
1665         oldPausing = pausing;
1666         MarkMenuItem("Mode.Pause", pausing);
1667
1668         if (appData.showButtonBar) {
1669           /* Always toggle, don't set.  Previous code messes up when
1670              invoked while the button is pressed, as releasing it
1671              toggles the state again. */
1672             GdkColor color;
1673             gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1674             gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1675         }
1676     }
1677
1678     wname = ModeToWidgetName(oldmode);
1679     if (wname != NULL) {
1680         MarkMenuItem(wname, False);
1681     }
1682     wname = ModeToWidgetName(gameMode);
1683     if (wname != NULL) {
1684         MarkMenuItem(wname, True);
1685     }
1686     oldmode = gameMode;
1687     MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1688
1689     /* Maybe all the enables should be handled here, not just this one */
1690     EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1691
1692     DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1693 }
1694
1695
1696 /*
1697  * Button/menu procedures
1698  */
1699
1700 void CopyFileToClipboard(gchar *filename)
1701 {
1702     gchar *selection_tmp;
1703     GtkClipboard *cb;
1704
1705     // read the file
1706     FILE* f = fopen(filename, "r");
1707     long len;
1708     size_t count;
1709     if (f == NULL) return;
1710     fseek(f, 0, 2);
1711     len = ftell(f);
1712     rewind(f);
1713     selection_tmp = g_try_malloc(len + 1);
1714     if (selection_tmp == NULL) {
1715         printf("Malloc failed in CopyFileToClipboard\n");
1716         return;
1717     }
1718     count = fread(selection_tmp, 1, len, f);
1719     fclose(f);
1720     if (len != count) {
1721       g_free(selection_tmp);
1722       return;
1723     }
1724     selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1725
1726     // copy selection_tmp to clipboard
1727     GdkDisplay *gdisp = gdk_display_get_default();
1728     if (!gdisp) {
1729         g_free(selection_tmp);
1730         return;
1731     }
1732     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1733     gtk_clipboard_set_text(cb, selection_tmp, -1);
1734     g_free(selection_tmp);
1735 }
1736
1737 void
1738 CopySomething (char *src)
1739 {
1740     GdkDisplay *gdisp = gdk_display_get_default();
1741     GtkClipboard *cb;
1742     if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1743     if (gdisp == NULL) return;
1744     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1745     gtk_clipboard_set_text(cb, src, -1);
1746 }
1747
1748 void
1749 PastePositionProc ()
1750 {
1751     GdkDisplay *gdisp = gdk_display_get_default();
1752     GtkClipboard *cb;
1753     gchar *fenstr;
1754
1755     if (gdisp == NULL) return;
1756     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1757     fenstr = gtk_clipboard_wait_for_text(cb);
1758     if (fenstr==NULL) return; // nothing had been selected to copy
1759     EditPositionPasteFEN(fenstr);
1760     return;
1761 }
1762
1763 void
1764 PasteGameProc ()
1765 {
1766     gchar *text=NULL;
1767     GtkClipboard *cb;
1768     guint len=0;
1769     FILE* f;
1770
1771     // get game from clipboard
1772     GdkDisplay *gdisp = gdk_display_get_default();
1773     if (gdisp == NULL) return;
1774     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1775     text = gtk_clipboard_wait_for_text(cb);
1776     if (text == NULL) return; // nothing to paste
1777     len = strlen(text);
1778
1779     // write to temp file
1780     if (text == NULL || len == 0) {
1781       return; //nothing to paste
1782     }
1783     f = fopen(gamePasteFilename, "w");
1784     if (f == NULL) {
1785       DisplayError(_("Can't open temp file"), errno);
1786       return;
1787     }
1788     fwrite(text, 1, len, f);
1789     fclose(f);
1790
1791     // load from file
1792     LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1793     return;
1794 }
1795
1796
1797 #ifdef TODO_GTK
1798 void
1799 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1800 {
1801     QuitProc();
1802 }
1803 #endif
1804
1805 void MoveTypeInProc(eventkey)
1806     GdkEventKey  *eventkey;
1807 {
1808     char buf[10];
1809
1810     // ingnore if ctrl, alt, or meta is pressed
1811     if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1812         return;
1813     }
1814
1815     buf[0]=eventkey->keyval;
1816     buf[1]='\0';
1817     if (eventkey->keyval > 32 && eventkey->keyval < 256)
1818         ConsoleAutoPopUp (buf);
1819 }
1820
1821 #ifdef TODO_GTK
1822 void
1823 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1824 {
1825         if (!TempBackwardActive) {
1826                 TempBackwardActive = True;
1827                 BackwardEvent();
1828         }
1829 }
1830
1831 void
1832 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1833 {
1834         /* Check to see if triggered by a key release event for a repeating key.
1835          * If so the next queued event will be a key press of the same key at the same time */
1836         if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1837                 XEvent next;
1838                 XPeekEvent(xDisplay, &next);
1839                 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1840                         next.xkey.keycode == event->xkey.keycode)
1841                                 return;
1842         }
1843     ForwardEvent();
1844         TempBackwardActive = False;
1845 }
1846 #endif
1847
1848 void
1849 ManProc ()
1850 {   // called from menu
1851 #ifdef __APPLE__
1852     char buf[MSG_SIZ];
1853     snprintf(buf, MSG_SIZ, "%s ./man.command", appData.sysOpen);
1854     system(buf);
1855 #else
1856     system("xterm -e man xboard &");
1857 #endif
1858 }
1859
1860 void
1861 SetWindowTitle (char *text, char *title, char *icon)
1862 {
1863 #ifdef TODO_GTK
1864     Arg args[16];
1865     int i;
1866     if (appData.titleInWindow) {
1867         i = 0;
1868         XtSetArg(args[i], XtNlabel, text);   i++;
1869         XtSetValues(titleWidget, args, i);
1870     }
1871     i = 0;
1872     XtSetArg(args[i], XtNiconName, (XtArgVal) icon);    i++;
1873     XtSetArg(args[i], XtNtitle, (XtArgVal) title);      i++;
1874     XtSetValues(shellWidget, args, i);
1875     XSync(xDisplay, False);
1876 #endif
1877     if (appData.titleInWindow) {
1878         SetWidgetLabel(titleWidget, text);
1879     }
1880     gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1881 }
1882
1883
1884 void
1885 DisplayIcsInteractionTitle (String message)
1886 {
1887 #ifdef TODO_GTK
1888   if (oldICSInteractionTitle == NULL) {
1889     /* Magic to find the old window title, adapted from vim */
1890     char *wina = getenv("WINDOWID");
1891     if (wina != NULL) {
1892       Window win = (Window) atoi(wina);
1893       Window root, parent, *children;
1894       unsigned int nchildren;
1895       int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
1896       for (;;) {
1897         if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
1898         if (!XQueryTree(xDisplay, win, &root, &parent,
1899                         &children, &nchildren)) break;
1900         if (children) XFree((void *)children);
1901         if (parent == root || parent == 0) break;
1902         win = parent;
1903       }
1904       XSetErrorHandler(oldHandler);
1905     }
1906     if (oldICSInteractionTitle == NULL) {
1907       oldICSInteractionTitle = "xterm";
1908     }
1909   }
1910   printf("\033]0;%s\007", message);
1911   fflush(stdout);
1912 #endif
1913 }
1914
1915
1916 void
1917 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
1918 {
1919     GtkWidget *w = (GtkWidget *) opt->handle;
1920     GdkColor col;
1921     char *markup;
1922     char bgcolor[10];
1923     char fgcolor[10];
1924
1925     if (highlight) {
1926         strcpy(bgcolor, "black");
1927         strcpy(fgcolor, "white");
1928     } else {
1929         strcpy(bgcolor, "white");
1930         strcpy(fgcolor, "black");
1931     }
1932     if (timer > 0 &&
1933         appData.lowTimeWarning &&
1934         (timer / 1000) < appData.icsAlarmTime) {
1935         strcpy(fgcolor, appData.lowTimeWarningColor);
1936     }
1937
1938     gdk_color_parse( bgcolor, &col );
1939     gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
1940
1941     if (appData.clockMode) {
1942         markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>", appData.clockFont,
1943                                          bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
1944 //        markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
1945 //                                       bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
1946     } else {
1947         markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s  </span>", appData.clockFont,
1948                                          bgcolor, fgcolor, color);
1949 //        markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s  </span>",
1950 //                                       bgcolor, fgcolor, color);
1951     }
1952     gtk_label_set_markup(GTK_LABEL(w), markup);
1953     g_free(markup);
1954 }
1955
1956 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
1957
1958 void
1959 SetClockIcon (int color)
1960 {
1961     GdkPixbuf *pm = *clockIcons[color];
1962     if (mainwindowIcon != pm) {
1963         mainwindowIcon = pm;
1964         gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1965     }
1966 }
1967
1968 #define INPUT_SOURCE_BUF_SIZE 8192
1969
1970 typedef struct {
1971     CPKind kind;
1972     int fd;
1973     int lineByLine;
1974     char *unused;
1975     InputCallback func;
1976     guint sid;
1977     char buf[INPUT_SOURCE_BUF_SIZE];
1978     VOIDSTAR closure;
1979 } InputSource;
1980
1981 gboolean
1982 DoInputCallback(io, cond, data)
1983      GIOChannel  *io;
1984      GIOCondition cond;
1985      gpointer    *data;
1986 {
1987   /* read input from one of the input source (for example a chess program, ICS, etc).
1988    * and call a function that will handle the input
1989    */
1990
1991     int count;
1992     int error;
1993     char *p, *q;
1994
1995     /* All information (callback function, file descriptor, etc) is
1996      * saved in an InputSource structure
1997      */
1998     InputSource *is = (InputSource *) data;
1999
2000     if (is->lineByLine) {
2001         count = read(is->fd, is->unused,
2002                      INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
2003         if (count <= 0) {
2004             if(count == 0 && is->kind == CPReal && shells[ChatDlg]) { // [HGM] absence of terminal is no error if ICS Console present
2005                 RemoveInputSource(is); // cease reading stdin
2006                 stdoutClosed = TRUE;   // suppress future output
2007                 return True;
2008             } 
2009             (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
2010             return True;
2011         }
2012         is->unused += count;
2013         p = is->buf;
2014         /* break input into lines and call the callback function on each
2015          * line
2016          */
2017         while (p < is->unused) {
2018             q = memchr(p, '\n', is->unused - p);
2019             if (q == NULL) break;
2020             q++;
2021             (is->func)(is, is->closure, p, q - p, 0);
2022             p = q;
2023         }
2024         /* remember not yet used part of the buffer */
2025         q = is->buf;
2026         while (p < is->unused) {
2027             *q++ = *p++;
2028         }
2029         is->unused = q;
2030     } else {
2031       /* read maximum length of input buffer and send the whole buffer
2032        * to the callback function
2033        */
2034         count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2035         if (count == -1)
2036           error = errno;
2037         else
2038           error = 0;
2039         (is->func)(is, is->closure, is->buf, count, error);
2040     }
2041     return True; // Must return true or the watch will be removed
2042 }
2043
2044 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2045      ProcRef pr;
2046      int lineByLine;
2047      InputCallback func;
2048      VOIDSTAR closure;
2049 {
2050     InputSource *is;
2051     GIOChannel *channel;
2052     ChildProc *cp = (ChildProc *) pr;
2053
2054     is = (InputSource *) calloc(1, sizeof(InputSource));
2055     is->lineByLine = lineByLine;
2056     is->func = func;
2057     if (pr == NoProc) {
2058         is->kind = CPReal;
2059         is->fd = fileno(stdin);
2060     } else {
2061         is->kind = cp->kind;
2062         is->fd = cp->fdFrom;
2063     }
2064     if (lineByLine)
2065       is->unused = is->buf;
2066     else
2067       is->unused = NULL;
2068
2069    /* GTK-TODO: will this work on windows?*/
2070
2071     channel = g_io_channel_unix_new(is->fd);
2072     g_io_channel_set_close_on_unref (channel, TRUE);
2073     is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2074
2075     is->closure = closure;
2076     return (InputSourceRef) is;
2077 }
2078
2079
2080 void
2081 RemoveInputSource(isr)
2082      InputSourceRef isr;
2083 {
2084     InputSource *is = (InputSource *) isr;
2085
2086     if (is->sid == 0) return;
2087     g_source_remove(is->sid);
2088     is->sid = 0;
2089     return;
2090 }
2091
2092 #ifndef HAVE_USLEEP
2093
2094 static Boolean frameWaiting;
2095
2096 static RETSIGTYPE
2097 FrameAlarm (int sig)
2098 {
2099   frameWaiting = False;
2100   /* In case System-V style signals.  Needed?? */
2101   signal(SIGALRM, FrameAlarm);
2102 }
2103
2104 void
2105 FrameDelay (int time)
2106 {
2107   struct itimerval delay;
2108
2109   if (time > 0) {
2110     frameWaiting = True;
2111     signal(SIGALRM, FrameAlarm);
2112     delay.it_interval.tv_sec =
2113       delay.it_value.tv_sec = time / 1000;
2114     delay.it_interval.tv_usec =
2115       delay.it_value.tv_usec = (time % 1000) * 1000;
2116     setitimer(ITIMER_REAL, &delay, NULL);
2117     while (frameWaiting) pause();
2118     delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2119     delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2120     setitimer(ITIMER_REAL, &delay, NULL);
2121   }
2122 }
2123
2124 #else
2125
2126 void
2127 FrameDelay (int time)
2128 {
2129 #ifdef TODO_GTK
2130   XSync(xDisplay, False);
2131 #endif
2132 //  gtk_main_iteration_do(False);
2133
2134   if (time > 0)
2135     usleep(time * 1000);
2136 }
2137
2138 #endif
2139
2140 static void
2141 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2142 {
2143     char buf[MSG_SIZ], *logoName = buf;
2144     if(appData.logo[n][0]) {
2145         logoName = appData.logo[n];
2146     } else if(appData.autoLogo) {
2147         if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2148             sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2149         } else if(appData.directory[n] && appData.directory[n][0]) {
2150             sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2151         }
2152     }
2153     if(logoName[0])
2154         { ASSIGN(cps->programLogo, logoName); }
2155 }
2156
2157 void
2158 UpdateLogos (int displ)
2159 {
2160     if(optList[W_WHITE-1].handle == NULL) return;
2161     LoadLogo(&first, 0, 0);
2162     LoadLogo(&second, 1, appData.icsActive);
2163     if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2164     return;
2165 }
2166
2167 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2168      char *label;
2169      char *def;
2170      char *filter;
2171      FileProc proc;
2172      char *openMode;
2173      Boolean pathFlag;
2174      char **name;
2175      FILE **fp;
2176 {
2177   GtkWidget     *dialog;
2178   GtkFileFilter *gtkfilter;
2179   GtkFileFilter *gtkfilter_all;
2180   char space[]     = " ";
2181   char fileext[10] = "";
2182   char *result     = NULL;
2183   char *cp;
2184
2185   /* make a copy of the filter string, so that strtok can work with it*/
2186   cp = strdup(filter);
2187
2188   /* add filters for file extensions */
2189   gtkfilter     = gtk_file_filter_new();
2190   gtkfilter_all = gtk_file_filter_new();
2191
2192   /* one filter to show everything */
2193   gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2194   gtk_file_filter_set_name   (gtkfilter_all, "All Files");
2195
2196   /* add filter if present */
2197   result = strtok(cp, space);
2198   while( result != NULL  ) {
2199     snprintf(fileext,10,"*%s",result);
2200     result = strtok( NULL, space );
2201     gtk_file_filter_add_pattern(gtkfilter, fileext);
2202   };
2203
2204   /* second filter to only show what's useful */
2205   gtk_file_filter_set_name (gtkfilter,filter);
2206
2207   if (openMode[0] == 'r')
2208     {
2209       dialog = gtk_file_chooser_dialog_new (label,
2210                                             NULL,
2211                                             GTK_FILE_CHOOSER_ACTION_OPEN,
2212                                             GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2213                                             GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2214                                             NULL);
2215     }
2216   else
2217     {
2218       dialog = gtk_file_chooser_dialog_new (label,
2219                                             NULL,
2220                                             GTK_FILE_CHOOSER_ACTION_SAVE,
2221                                             GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2222                                             GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2223                                             NULL);
2224       /* add filename suggestions */
2225       if (strlen(def) > 0 )
2226         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2227
2228       //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2229     }
2230
2231   /* add filters */
2232   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2233   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2234   /* activate filter */
2235   gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2236
2237   if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2238     {
2239       char *filename;
2240       FILE *f;
2241
2242       filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2243
2244       //see loadgamepopup
2245       f = fopen(filename, openMode);
2246       if (f == NULL)
2247         {
2248           DisplayError(_("Failed to open file"), errno);
2249         }
2250       else
2251         {
2252           /* TODO add indec */
2253             *fp = f;
2254             ASSIGN(*name, filename);
2255             ScheduleDelayedEvent(DelayedLoad, 50);
2256         }
2257       g_free (filename);
2258     };
2259
2260   gtk_widget_destroy (dialog);
2261   ModeHighlight();
2262
2263   free(cp);
2264   return;
2265
2266 }