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