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