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