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