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