Fix clipping of GTK menu-bar labels for broad boards
[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 #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 (i < 7) {
1024             /* Find some defaults; use the nearest known size */
1025             SizeDefaults *szd, *nearest;
1026             int distance = 99999;
1027             nearest = szd = sizeDefaults;
1028             while (szd->name != NULL) {
1029                 if (abs(szd->squareSize - squareSize) < distance) {
1030                     nearest = szd;
1031                     distance = abs(szd->squareSize - squareSize);
1032                     if (distance == 0) break;
1033                 }
1034                 szd++;
1035             }
1036             if (i < 2) lineGap = nearest->lineGap;
1037             if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
1038             if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
1039             if (i < 5) fontPxlSize = nearest->fontPxlSize;
1040             if (i < 6) smallLayout = nearest->smallLayout;
1041             if (i < 7) tinyLayout = nearest->tinyLayout;
1042         }
1043     } else {
1044         SizeDefaults *szd = sizeDefaults;
1045         if (*appData.boardSize == NULLCHAR) {
1046 //            GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow)); // TODO: this does not work, as no mainwindow yet
1047             GdkScreen *screen = gdk_screen_get_default();
1048             guint screenwidth = gdk_screen_get_width(screen);
1049             guint screenheight = gdk_screen_get_height(screen);
1050             while (screenwidth  < (szd->minScreenSize*BOARD_WIDTH  + 4)/8 ||
1051                    screenheight < (szd->minScreenSize*BOARD_HEIGHT + 4)/8) {
1052               szd++;
1053             }
1054             if (szd->name == NULL) szd--;
1055             appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
1056         } else {
1057             while (szd->name != NULL &&
1058                    StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
1059             if (szd->name == NULL) {
1060                 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
1061                         programName, appData.boardSize);
1062                 exit(2);
1063             }
1064         }
1065         squareSize = szd->squareSize;
1066         lineGap = szd->lineGap;
1067         clockFontPxlSize = szd->clockFontPxlSize;
1068         coordFontPxlSize = szd->coordFontPxlSize;
1069         fontPxlSize = szd->fontPxlSize;
1070         smallLayout = szd->smallLayout;
1071         tinyLayout = szd->tinyLayout;
1072         // [HGM] font: use defaults from settings file if available and not overruled
1073     }
1074     if(BOARD_WIDTH != 8) {
1075         squareSize = (squareSize*8 + BOARD_WIDTH/2)/BOARD_WIDTH; // keep width the same
1076         lineGap = (squareSize < 37 ? 1 : squareSize < 59 ? 2 : squareSize < 116 ? 3 : 4);
1077 printf("squareSize=%d lineGap=%d\n",squareSize,lineGap);
1078     }
1079
1080     defaultLineGap = lineGap;
1081     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
1082
1083     /* [HR] height treated separately (hacked) */
1084     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
1085 //    boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1086
1087     /*
1088      * Determine what fonts to use.
1089      */
1090     InitializeFonts((2*clockFontPxlSize+1)/3, coordFontPxlSize, fontPxlSize);
1091
1092     /*
1093      * Detect if there are not enough colors available and adapt.
1094      */
1095 #ifdef TODO_GTK
1096     if (DefaultDepth(xDisplay, xScreen) <= 2) {
1097       appData.monoMode = True;
1098     }
1099 #endif
1100
1101     forceMono = MakeColors();
1102
1103     if (forceMono) {
1104       fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1105               programName);
1106         appData.monoMode = True;
1107     }
1108
1109     ParseIcsTextColors();
1110
1111     /*
1112      * widget hierarchy
1113      */
1114     if (tinyLayout) {
1115         layoutName = "tinyLayout";
1116     } else if (smallLayout) {
1117         layoutName = "smallLayout";
1118     } else {
1119         layoutName = "normalLayout";
1120     }
1121
1122     if(appData.logoSize) appData.logoSize = boardWidth/4-3;
1123     wpMain.width = -1; // prevent popup sizes window
1124     optList = BoardPopUp(squareSize, lineGap, (void*)
1125 #ifdef TODO_GTK
1126 #if ENABLE_NLS
1127                                                 &clockFontSet);
1128 #else
1129                                                 clockFontStruct);
1130 #endif
1131 #else
1132 0);
1133 #endif
1134     InitDrawingHandle(optList + W_BOARD);
1135     shellWidget      = shells[BoardWindow];
1136     currBoard        = &optList[W_BOARD];
1137     boardWidget      = optList[W_BOARD].handle;
1138     menuBarWidget    = optList[W_MENU].handle;
1139     dropMenu         = optList[W_DROP].handle;
1140     titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1141 #ifdef TODO_GTK
1142     formWidget  = XtParent(boardWidget);
1143     XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1144     XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1145     XtGetValues(optList[W_WHITE].handle, args, 2);
1146     if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1147       XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1148       XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1149       XtGetValues(optList[W_PAUSE].handle, args, 2);
1150     }
1151 #endif
1152
1153     // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1154     //       not need to go into InitDrawingSizes().
1155
1156     InitMenuMarkers();
1157
1158     // add accelerators to main shell
1159     gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1160
1161     /*
1162      * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1163      */
1164     WhiteIcon  = LoadIconFile("icon_white");
1165     BlackIcon  = LoadIconFile("icon_black");
1166     SetClockIcon(0); // sets white icon
1167
1168
1169     /*
1170      * Create a cursor for the board widget.
1171      */
1172 #ifdef TODO_GTK
1173     window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1174     XChangeWindowAttributes(xDisplay, xBoardWindow,
1175                             CWCursor, &window_attributes);
1176 #endif
1177
1178     /*
1179      * Inhibit shell resizing.
1180      */
1181 #ifdef TODO_GTK
1182     shellArgs[0].value = (XtArgVal) &w;
1183     shellArgs[1].value = (XtArgVal) &h;
1184     XtGetValues(shellWidget, shellArgs, 2);
1185     shellArgs[4].value = shellArgs[2].value = w;
1186     shellArgs[5].value = shellArgs[3].value = h;
1187 //    XtSetValues(shellWidget, &shellArgs[2], 4);
1188 #endif
1189     {
1190         // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1191         // It wil only become known asynchronously, when we first write a string into it.
1192         // This will then change the clock widget height, which triggers resizing the top-level window
1193         // and a configure event. Only then can we know the total height of the top-level window,
1194         // and calculate the height we need. The clockKludge flag suppresses all resizing until
1195         // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1196         int hc;
1197         GtkAllocation a;
1198         gtk_widget_get_allocation(shells[BoardWindow], &a);
1199         w = a.width; h = a.height;
1200         gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1201         clockKludge = hc = a.height;
1202         gtk_widget_get_allocation(boardWidget, &a);
1203         marginW =  w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1204         marginH =  h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1205     }
1206
1207     CreateAnyPieces(1);
1208     CreateGrid();
1209
1210     if(appData.logoSize)
1211     {   // locate and read user logo
1212         char buf[MSG_SIZ], name[MSG_SIZ];
1213         snprintf(name, MSG_SIZ, "/home/%s", UserName());
1214         if(!FindLogo(name, ".logo", buf))
1215             FindLogo(appData.logoDir, name + 6, buf);
1216         ASSIGN(userLogo, buf);
1217     }
1218
1219     if (appData.animate || appData.animateDragging)
1220       CreateAnimVars();
1221
1222     g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1223     g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1224
1225     /* [AS] Restore layout */
1226     if( wpMoveHistory.visible ) {
1227       HistoryPopUp();
1228     }
1229
1230     if( wpEvalGraph.visible )
1231       {
1232         EvalGraphPopUp();
1233       };
1234
1235     if( wpEngineOutput.visible ) {
1236       EngineOutputPopUp();
1237     }
1238
1239     if( wpConsole.visible && appData.icsActive ) {
1240       ChatProc();
1241       BoardToTop();
1242     }
1243
1244     gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1245     InitPosition(TRUE);
1246
1247     InitBackEnd2();
1248
1249     if (errorExitStatus == -1) {
1250         if (appData.icsActive) {
1251             /* We now wait until we see "login:" from the ICS before
1252                sending the logon script (problems with timestamp otherwise) */
1253             /*ICSInitScript();*/
1254             if (appData.icsInputBox) ICSInputBoxPopUp();
1255         }
1256
1257     #ifdef SIGWINCH
1258     signal(SIGWINCH, TermSizeSigHandler);
1259     #endif
1260         signal(SIGINT, IntSigHandler);
1261         signal(SIGTERM, IntSigHandler);
1262         if (*appData.cmailGameName != NULLCHAR) {
1263             signal(SIGUSR1, CmailSigHandler);
1264         }
1265     }
1266
1267     UpdateLogos(TRUE);
1268 //    XtSetKeyboardFocus(shellWidget, formWidget);
1269 #ifdef TODO_GTK
1270     XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1271 #endif
1272
1273     /* check for GTK events and process them */
1274 //    gtk_main();
1275 while(1) {
1276 gtk_main_iteration();
1277 }
1278
1279     if (appData.debugMode) fclose(debugFP); // [DM] debug
1280     return 0;
1281 }
1282
1283 void
1284 DoEvents ()
1285 {
1286     while(gtk_events_pending()) gtk_main_iteration();
1287 }
1288
1289 RETSIGTYPE
1290 TermSizeSigHandler (int sig)
1291 {
1292     update_ics_width();
1293 }
1294
1295 RETSIGTYPE
1296 IntSigHandler (int sig)
1297 {
1298     ExitEvent(sig);
1299 }
1300
1301 RETSIGTYPE
1302 CmailSigHandler (int sig)
1303 {
1304     int dummy = 0;
1305     int error;
1306
1307     signal(SIGUSR1, SIG_IGN);   /* suspend handler     */
1308
1309     /* Activate call-back function CmailSigHandlerCallBack()             */
1310     OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1311
1312     signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1313 }
1314
1315 void
1316 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1317 {
1318     BoardToTop();
1319     ReloadCmailMsgEvent(TRUE);  /* Reload cmail msg  */
1320 }
1321 /**** end signal code ****/
1322
1323
1324 #define Abs(n) ((n)<0 ? -(n) : (n))
1325
1326 char *
1327 InsertPxlSize (char *pattern, int targetPxlSize)
1328 {
1329     char buf[MSG_SIZ];
1330     snprintf(buf, MSG_SIZ, pattern, targetPxlSize); // pattern is something like "Sans Bold %d"
1331     return strdup(buf);
1332 }
1333
1334 #ifdef ENABLE_NLS
1335 #ifdef TODO_GTK
1336 char *
1337 InsertPxlSize (char *pattern, int targetPxlSize)
1338 {
1339     char *base_fnt_lst, strInt[12], *p, *q;
1340     int alternatives, i, len, strIntLen;
1341
1342     /*
1343      * Replace the "*" (if present) in the pixel-size slot of each
1344      * alternative with the targetPxlSize.
1345      */
1346     p = pattern;
1347     alternatives = 1;
1348     while ((p = strchr(p, ',')) != NULL) {
1349       alternatives++;
1350       p++;
1351     }
1352     snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1353     strIntLen = strlen(strInt);
1354     base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1355
1356     p = pattern;
1357     q = base_fnt_lst;
1358     while (alternatives--) {
1359       char *comma = strchr(p, ',');
1360       for (i=0; i<14; i++) {
1361         char *hyphen = strchr(p, '-');
1362         if (!hyphen) break;
1363         if (comma && hyphen > comma) break;
1364         len = hyphen + 1 - p;
1365         if (i == 7 && *p == '*' && len == 2) {
1366           p += len;
1367           memcpy(q, strInt, strIntLen);
1368           q += strIntLen;
1369           *q++ = '-';
1370         } else {
1371           memcpy(q, p, len);
1372           p += len;
1373           q += len;
1374         }
1375       }
1376       if (!comma) break;
1377       len = comma + 1 - p;
1378       memcpy(q, p, len);
1379       p += len;
1380       q += len;
1381     }
1382     strcpy(q, p);
1383
1384     return base_fnt_lst;
1385 }
1386 #endif
1387
1388 #ifdef TODO_GTK
1389 XFontSet
1390 CreateFontSet (char *base_fnt_lst)
1391 {
1392     XFontSet fntSet;
1393     char **missing_list;
1394     int missing_count;
1395     char *def_string;
1396
1397     fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1398                             &missing_list, &missing_count, &def_string);
1399     if (appData.debugMode) {
1400       int i, count;
1401       XFontStruct **font_struct_list;
1402       char **font_name_list;
1403       fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1404       if (fntSet) {
1405         fprintf(debugFP, " got list %s, locale %s\n",
1406                 XBaseFontNameListOfFontSet(fntSet),
1407                 XLocaleOfFontSet(fntSet));
1408         count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1409         for (i = 0; i < count; i++) {
1410           fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1411         }
1412       }
1413       for (i = 0; i < missing_count; i++) {
1414         fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1415       }
1416     }
1417     if (fntSet == NULL) {
1418       fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1419       exit(2);
1420     }
1421     return fntSet;
1422 }
1423 #endif
1424 #else // not ENABLE_NLS
1425 /*
1426  * Find a font that matches "pattern" that is as close as
1427  * possible to the targetPxlSize.  Prefer fonts that are k
1428  * pixels smaller to fonts that are k pixels larger.  The
1429  * pattern must be in the X Consortium standard format,
1430  * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1431  * The return value should be freed with XtFree when no
1432  * longer needed.
1433  */
1434 #ifdef TODO_GTK
1435 char *
1436 FindFont (char *pattern, int targetPxlSize)
1437 {
1438     char **fonts, *p, *best, *scalable, *scalableTail;
1439     int i, j, nfonts, minerr, err, pxlSize;
1440
1441     fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1442     if (nfonts < 1) {
1443         fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1444                 programName, pattern);
1445         exit(2);
1446     }
1447
1448     best = fonts[0];
1449     scalable = NULL;
1450     minerr = 999999;
1451     for (i=0; i<nfonts; i++) {
1452         j = 0;
1453         p = fonts[i];
1454         if (*p != '-') continue;
1455         while (j < 7) {
1456             if (*p == NULLCHAR) break;
1457             if (*p++ == '-') j++;
1458         }
1459         if (j < 7) continue;
1460         pxlSize = atoi(p);
1461         if (pxlSize == 0) {
1462             scalable = fonts[i];
1463             scalableTail = p;
1464         } else {
1465             err = pxlSize - targetPxlSize;
1466             if (Abs(err) < Abs(minerr) ||
1467                 (minerr > 0 && err < 0 && -err == minerr)) {
1468                 best = fonts[i];
1469                 minerr = err;
1470             }
1471         }
1472     }
1473     if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1474         /* If the error is too big and there is a scalable font,
1475            use the scalable font. */
1476         int headlen = scalableTail - scalable;
1477         p = (char *) XtMalloc(strlen(scalable) + 10);
1478         while (isdigit(*scalableTail)) scalableTail++;
1479         sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1480     } else {
1481         p = (char *) XtMalloc(strlen(best) + 2);
1482         safeStrCpy(p, best, strlen(best)+1 );
1483     }
1484     if (appData.debugMode) {
1485         fprintf(debugFP, "resolved %s at pixel size %d\n  to %s\n",
1486                 pattern, targetPxlSize, p);
1487     }
1488     XFreeFontNames(fonts);
1489     return p;
1490 }
1491 #endif
1492 #endif
1493
1494 void
1495 MarkMenuItem (char *menuRef, int state)
1496 {
1497     MenuItem *item = MenuNameToItem(menuRef);
1498
1499     if(item && item->handle) {
1500         ((GtkCheckMenuItem *) (item->handle))->active = state;
1501     }
1502     SYNC_MENUBAR;
1503 }
1504
1505 void
1506 EnableNamedMenuItem (char *menuRef, int state)
1507 {
1508     MenuItem *item = MenuNameToItem(menuRef);
1509
1510     if(item && item->handle) gtk_widget_set_sensitive(item->handle, state);
1511     SYNC_MENUBAR;
1512 }
1513
1514 void
1515 EnableButtonBar (int state)
1516 {
1517 #ifdef TODO_GTK
1518     XtSetSensitive(optList[W_BUTTON].handle, state);
1519 #endif
1520 }
1521
1522
1523 void
1524 SetMenuEnables (Enables *enab)
1525 {
1526   while (enab->name != NULL) {
1527     EnableNamedMenuItem(enab->name, enab->value);
1528     enab++;
1529   }
1530 }
1531
1532 gboolean KeyPressProc(window, eventkey, data)
1533      GtkWindow *window;
1534      GdkEventKey  *eventkey;
1535      gpointer data;
1536 {
1537
1538     MoveTypeInProc(eventkey); // pop up for typed in moves
1539
1540 #ifdef TODO_GTK
1541     /* check for other key values */
1542     switch(eventkey->keyval) {
1543         case GDK_question:
1544           AboutGameEvent();
1545           break;
1546         default:
1547           break;
1548     }
1549 #endif
1550     return False;
1551 }
1552 #ifdef TODO_GTK
1553 void
1554 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1555 {   // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1556     MenuItem *item;
1557     if(*nprms == 0) return;
1558     item = MenuNameToItem(prms[0]);
1559     if(item) ((MenuProc *) item->proc) ();
1560 }
1561 #endif
1562
1563 void
1564 SetupDropMenu ()
1565 {
1566 #ifdef TODO_GTK
1567     int i, j, count;
1568     char label[32];
1569     Arg args[16];
1570     Widget entry;
1571     char* p;
1572
1573     for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1574         entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1575         p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1576                    dmEnables[i].piece);
1577         XtSetSensitive(entry, p != NULL || !appData.testLegality
1578                        /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1579                                        && !appData.icsActive));
1580         count = 0;
1581         while (p && *p++ == dmEnables[i].piece) count++;
1582         snprintf(label, sizeof(label), "%s  %d", dmEnables[i].widget, count);
1583         j = 0;
1584         XtSetArg(args[j], XtNlabel, label); j++;
1585         XtSetValues(entry, args, j);
1586     }
1587 #endif
1588 }
1589
1590 static void
1591 do_flash_delay (unsigned long msec)
1592 {
1593     TimeDelay(msec);
1594 }
1595
1596 void
1597 FlashDelay (int flash_delay)
1598 {
1599         if(flash_delay) do_flash_delay(flash_delay);
1600 }
1601
1602 double
1603 Fraction (int x, int start, int stop)
1604 {
1605    double f = ((double) x - start)/(stop - start);
1606    if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1607    return f;
1608 }
1609
1610 static WindowPlacement wpNew;
1611
1612 void
1613 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1614 {
1615     int touch=0, fudge = 4, f = 3;
1616     GetActualPlacement(sh, wp);
1617     if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x)         < fudge) touch = 1; else // right touch
1618     if(abs(wp->x + wp->width + 2*frameX - f - wpMain.x)            < fudge) touch = 2; else // left touch
1619     if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1620     if(abs(wp->y + wp->height + frameX + frameY - f - wpMain.y)    < fudge) touch = 4;      // top touch
1621 //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);
1622     if(!touch ) return; // only windows that touch co-move
1623     if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1624         int heightInc = wpNew.height - wpMain.height;
1625         double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1626         double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1627         wp->y += fracTop * heightInc;
1628         heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1629 #ifdef TODO_GTK
1630         if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1631 #endif
1632         wp->height += heightInc;
1633     } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1634         int widthInc = wpNew.width - wpMain.width;
1635         double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1636         double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1637         wp->y += fracLeft * widthInc;
1638         widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1639 #ifdef TODO_GTK
1640         if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1641 #endif
1642         wp->width += widthInc;
1643     }
1644     wp->x += wpNew.x - wpMain.x;
1645     wp->y += wpNew.y - wpMain.y;
1646     if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1647     if(touch == 3) wp->y += wpNew.height - wpMain.height;
1648 #ifdef TODO_GTK
1649     XtSetArg(args[j], XtNx, wp->x); j++;
1650     XtSetArg(args[j], XtNy, wp->y); j++;
1651     XtSetValues(sh, args, j);
1652 #endif
1653         gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1654 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1655         gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1656 }
1657
1658 void
1659 ReSize (WindowPlacement *wp)
1660 {
1661         GtkAllocation a;
1662         int sqx, sqy, w, h, lg = lineGap;
1663         static int first = 1;
1664         if(wp->width == wpMain.width && wp->height == wpMain.height && !first) return; // not sized
1665         gtk_widget_get_allocation(optList[W_DROP+1].handle, &a); // table that should contain everything
1666         w = a.width; h = a.height;
1667         gtk_widget_get_allocation(shellWidget, &a);
1668         if(a.width < w || a.height < h) { // outer window smaller than dialog content?
1669             w = a.width - w; h = a.height - h; // subtract matrgins, measured as table minus board dimensions
1670             gtk_widget_get_allocation(optList[W_BOARD].handle, &a);
1671             w += a.width; h += a.height;
1672         } else {
1673             gtk_widget_get_allocation(optList[W_BOARD].handle, &a);
1674             w = a.width; h = a.height;
1675         }
1676         sqx = (w - lg) / BOARD_WIDTH - lg;
1677         sqy = (h - lg) / BOARD_HEIGHT - lg;
1678         if(sqy < sqx) sqx = sqy;
1679         if(sqx < 20) return;
1680         if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1681             int oldSqx = sqx;
1682             lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1683             sqx = (w  - lg) / BOARD_WIDTH - lg;
1684             sqy = (h - lg) / BOARD_HEIGHT - lg;
1685             if(sqy < sqx) sqx = sqy;
1686             lg = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1687             if(sqx == oldSqx + 1 && lg == lineGap + 1) sqx = oldSqx, squareSize = 0; // prevent oscillations, force resize by kludge
1688         }
1689         if(sqx != squareSize && !first) {
1690             squareSize = sqx; // adopt new square size
1691             CreatePNGPieces(); // make newly scaled pieces
1692             InitDrawingSizes(0, 0); // creates grid etc.
1693         } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1694         w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1695         h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1696         if(optList[W_BOARD].max   > w) optList[W_BOARD].max = w;
1697         if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1698         first = appData.fixedSize;
1699 }
1700
1701 static guint delayedDragTag = 0;
1702
1703 void
1704 DragProc ()
1705 {
1706         static int busy;
1707         if(busy) return;
1708
1709         busy = 1;
1710         GetActualPlacement(shellWidget, &wpNew);
1711         if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1712            wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1713             busy = 0; return; // false alarm
1714         }
1715         ReSize(&wpNew);
1716         if(appData.useStickyWindows) {
1717             if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1718             if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1719             if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1720             if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1721             if(shellUp[ChatDlg]) CoDrag(shells[ChatDlg], &wpConsole);
1722         }
1723         wpMain = wpNew;
1724         DrawPosition(True, NULL);
1725         if(delayedDragTag) g_source_remove(delayedDragTag);
1726         delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1727         busy = 0;
1728 }
1729
1730 void
1731 DelayedDrag ()
1732 {
1733 //printf("old timr = %d\n", delayedDragTag);
1734     if(delayedDragTag) g_source_remove(delayedDragTag);
1735     delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1736 //printf("new timr = %d\n", delayedDragTag);
1737 }
1738
1739 static gboolean
1740 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1741 {
1742 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1743     // immediately
1744     wpNew.x = event->configure.x;
1745     wpNew.y = event->configure.y;
1746     wpNew.width  = event->configure.width;
1747     wpNew.height = event->configure.height;
1748     DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1749     return FALSE;
1750 }
1751
1752
1753
1754 /* Disable all user input other than deleting the window */
1755 static int frozen = 0;
1756
1757 void
1758 FreezeUI ()
1759 {
1760   if (frozen) return;
1761   /* Grab by a widget that doesn't accept input */
1762   gtk_grab_add(optList[W_MESSG].handle);
1763   frozen = 1;
1764 }
1765
1766 /* Undo a FreezeUI */
1767 void
1768 ThawUI ()
1769 {
1770   if (!frozen) return;
1771   gtk_grab_remove(optList[W_MESSG].handle);
1772   frozen = 0;
1773 }
1774
1775 void
1776 ModeHighlight ()
1777 {
1778     static int oldPausing = FALSE;
1779     static GameMode oldMode = (GameMode) -1;
1780     char *wname;
1781     if (!boardWidget) return;
1782
1783     if (pausing != oldPausing) {
1784         oldPausing = pausing;
1785         MarkMenuItem("Mode.Pause", pausing);
1786
1787         if (appData.showButtonBar) {
1788           /* Always toggle, don't set.  Previous code messes up when
1789              invoked while the button is pressed, as releasing it
1790              toggles the state again. */
1791             GdkColor color;
1792             gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1793             gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1794         }
1795     }
1796
1797     wname = ModeToWidgetName(oldMode);
1798     if (wname != NULL) {
1799         MarkMenuItem(wname, False);
1800     }
1801     wname = ModeToWidgetName(gameMode);
1802     if (wname != NULL) {
1803         MarkMenuItem(wname, True);
1804     }
1805     if(oldMode == TwoMachinesPlay) EnableNamedMenuItem("Mode.MachineMatch", True);
1806     MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1807     oldMode = gameMode;
1808
1809     /* Maybe all the enables should be handled here, not just this one */
1810     EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1811
1812     DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1813 }
1814
1815
1816 /*
1817  * Button/menu procedures
1818  */
1819
1820 void CopyFileToClipboard(gchar *filename)
1821 {
1822     gchar *selection_tmp;
1823     GtkClipboard *cb;
1824
1825     // read the file
1826     FILE* f = fopen(filename, "r");
1827     long len;
1828     size_t count;
1829     if (f == NULL) return;
1830     fseek(f, 0, 2);
1831     len = ftell(f);
1832     rewind(f);
1833     selection_tmp = g_try_malloc(len + 1);
1834     if (selection_tmp == NULL) {
1835         printf("Malloc failed in CopyFileToClipboard\n");
1836         return;
1837     }
1838     count = fread(selection_tmp, 1, len, f);
1839     fclose(f);
1840     if (len != count) {
1841       g_free(selection_tmp);
1842       return;
1843     }
1844     selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1845
1846     // copy selection_tmp to clipboard
1847     GdkDisplay *gdisp = gdk_display_get_default();
1848     if (!gdisp) {
1849         g_free(selection_tmp);
1850         return;
1851     }
1852     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1853     gtk_clipboard_set_text(cb, selection_tmp, -1);
1854     g_free(selection_tmp);
1855 }
1856
1857 void
1858 CopySomething (char *src)
1859 {
1860     GdkDisplay *gdisp = gdk_display_get_default();
1861     GtkClipboard *cb;
1862     if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1863     if (gdisp == NULL) return;
1864     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1865     gtk_clipboard_set_text(cb, src, -1);
1866 }
1867
1868 void
1869 PastePositionProc ()
1870 {
1871     GdkDisplay *gdisp = gdk_display_get_default();
1872     GtkClipboard *cb;
1873     gchar *fenstr;
1874
1875     if (gdisp == NULL) return;
1876     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1877     fenstr = gtk_clipboard_wait_for_text(cb);
1878     if (fenstr==NULL) return; // nothing had been selected to copy
1879     EditPositionPasteFEN(fenstr);
1880     return;
1881 }
1882
1883 void
1884 PasteGameProc ()
1885 {
1886     gchar *text=NULL;
1887     GtkClipboard *cb;
1888     guint len=0;
1889     FILE* f;
1890
1891     // get game from clipboard
1892     GdkDisplay *gdisp = gdk_display_get_default();
1893     if (gdisp == NULL) return;
1894     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1895     text = gtk_clipboard_wait_for_text(cb);
1896     if (text == NULL) return; // nothing to paste
1897     len = strlen(text);
1898
1899     // write to temp file
1900     if (text == NULL || len == 0) {
1901       return; //nothing to paste
1902     }
1903     f = fopen(gamePasteFilename, "w");
1904     if (f == NULL) {
1905       DisplayError(_("Can't open temp file"), errno);
1906       return;
1907     }
1908     fwrite(text, 1, len, f);
1909     fclose(f);
1910
1911     // load from file
1912     LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1913     return;
1914 }
1915
1916
1917 #ifdef TODO_GTK
1918 void
1919 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1920 {
1921     QuitProc();
1922 }
1923 #endif
1924
1925 void MoveTypeInProc(eventkey)
1926     GdkEventKey  *eventkey;
1927 {
1928     char buf[10];
1929
1930     // ingnore if ctrl, alt, or meta is pressed
1931     if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1932         return;
1933     }
1934
1935     buf[0]=eventkey->keyval;
1936     buf[1]='\0';
1937     if (eventkey->keyval > 32 && eventkey->keyval < 256 || *buf == 27)
1938         ConsoleAutoPopUp (buf);
1939 }
1940
1941 #ifdef TODO_GTK
1942 void
1943 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1944 {
1945         if (!TempBackwardActive) {
1946                 TempBackwardActive = True;
1947                 BackwardEvent();
1948         }
1949 }
1950
1951 void
1952 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1953 {
1954         /* Check to see if triggered by a key release event for a repeating key.
1955          * If so the next queued event will be a key press of the same key at the same time */
1956         if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1957                 XEvent next;
1958                 XPeekEvent(xDisplay, &next);
1959                 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1960                         next.xkey.keycode == event->xkey.keycode)
1961                                 return;
1962         }
1963     ForwardEvent();
1964         TempBackwardActive = False;
1965 }
1966 #endif
1967
1968 void
1969 ManProc ()
1970 {   // called from menu
1971 #ifdef OSXAPP
1972     char buf[MSG_SIZ];
1973     snprintf(buf, MSG_SIZ, "osascript -e 'tell application \"Terminal\"' -e 'activate' -e 'do script \"man %s/../man/man6/xboard.6\"' -e 'end tell'", dataDir);
1974     system(buf);
1975 #else
1976     system("xterm -e man xboard &");
1977 #endif
1978 }
1979
1980 void
1981 InfoProc ()
1982 {
1983     char buf[MSG_SIZ];
1984 #ifdef OSXAPP
1985     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);
1986 #else
1987         snprintf(buf, sizeof(buf), "xterm -e info --directory %s --directory . -f %s &",
1988                  INFODIR, INFOFILE);
1989 #endif
1990     system(buf);
1991 }
1992
1993
1994 void
1995 SetWindowTitle (char *text, char *title, char *icon)
1996 {
1997 #ifdef TODO_GTK
1998     Arg args[16];
1999     int i;
2000     if (appData.titleInWindow) {
2001         i = 0;
2002         XtSetArg(args[i], XtNlabel, text);   i++;
2003         XtSetValues(titleWidget, args, i);
2004     }
2005     i = 0;
2006     XtSetArg(args[i], XtNiconName, (XtArgVal) icon);    i++;
2007     XtSetArg(args[i], XtNtitle, (XtArgVal) title);      i++;
2008     XtSetValues(shellWidget, args, i);
2009     XSync(xDisplay, False);
2010 #endif
2011     if (appData.titleInWindow) {
2012         SetWidgetLabel(titleWidget, text);
2013     }
2014     gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
2015 }
2016
2017
2018 void
2019 DisplayIcsInteractionTitle (String message)
2020 {
2021 #ifdef TODO_GTK
2022   if (oldICSInteractionTitle == NULL) {
2023     /* Magic to find the old window title, adapted from vim */
2024     char *wina = getenv("WINDOWID");
2025     if (wina != NULL) {
2026       Window win = (Window) atoi(wina);
2027       Window root, parent, *children;
2028       unsigned int nchildren;
2029       int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
2030       for (;;) {
2031         if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
2032         if (!XQueryTree(xDisplay, win, &root, &parent,
2033                         &children, &nchildren)) break;
2034         if (children) XFree((void *)children);
2035         if (parent == root || parent == 0) break;
2036         win = parent;
2037       }
2038       XSetErrorHandler(oldHandler);
2039     }
2040     if (oldICSInteractionTitle == NULL) {
2041       oldICSInteractionTitle = "xterm";
2042     }
2043   }
2044   printf("\033]0;%s\007", message);
2045   fflush(stdout);
2046 #endif
2047 }
2048
2049
2050 void
2051 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
2052 {
2053     GtkWidget *w = (GtkWidget *) opt->handle;
2054     GdkColor col;
2055     char *markup;
2056     char bgcolor[10];
2057     char fgcolor[10];
2058
2059     if (highlight) {
2060         strcpy(bgcolor, "black");
2061         strcpy(fgcolor, "white");
2062     } else {
2063         strcpy(bgcolor, "white");
2064         strcpy(fgcolor, "black");
2065     }
2066     if (timer > 0 &&
2067         appData.lowTimeWarning &&
2068         (timer / 1000) < appData.icsAlarmTime) {
2069         strcpy(fgcolor, appData.lowTimeWarningColor);
2070     }
2071
2072     gdk_color_parse( bgcolor, &col );
2073     gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
2074
2075     if (appData.clockMode) {
2076         markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>", appData.clockFont,
2077                                          bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2078 //        markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
2079 //                                       bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2080     } else {
2081         markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s  </span>", appData.clockFont,
2082                                          bgcolor, fgcolor, color);
2083 //        markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s  </span>",
2084 //                                       bgcolor, fgcolor, color);
2085     }
2086     gtk_label_set_markup(GTK_LABEL(w), markup);
2087     g_free(markup);
2088 }
2089
2090 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
2091
2092 void
2093 SetClockIcon (int color)
2094 {
2095     GdkPixbuf *pm = *clockIcons[color];
2096     if (mainwindowIcon != pm) {
2097         mainwindowIcon = pm;
2098 #ifdef OSXAPP
2099         gtkosx_application_set_dock_icon_pixbuf(theApp, mainwindowIcon);
2100 #else
2101         gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
2102 #endif
2103     }
2104 }
2105
2106 #define INPUT_SOURCE_BUF_SIZE 8192
2107
2108 typedef struct {
2109     CPKind kind;
2110     int fd;
2111     int lineByLine;
2112     char *unused;
2113     InputCallback func;
2114     guint sid;
2115     char buf[INPUT_SOURCE_BUF_SIZE];
2116     VOIDSTAR closure;
2117 } InputSource;
2118
2119 gboolean
2120 DoInputCallback(io, cond, data)
2121      GIOChannel  *io;
2122      GIOCondition cond;
2123      gpointer    *data;
2124 {
2125   /* read input from one of the input source (for example a chess program, ICS, etc).
2126    * and call a function that will handle the input
2127    */
2128
2129     int count;
2130     int error;
2131     char *p, *q;
2132
2133     /* All information (callback function, file descriptor, etc) is
2134      * saved in an InputSource structure
2135      */
2136     InputSource *is = (InputSource *) data;
2137
2138     if (is->lineByLine) {
2139         count = read(is->fd, is->unused,
2140                      INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
2141         if (count <= 0) {
2142             if(count == 0 && is->kind == CPReal && shells[ChatDlg]) { // [HGM] absence of terminal is no error if ICS Console present
2143                 RemoveInputSource(is); // cease reading stdin
2144                 stdoutClosed = TRUE;   // suppress future output
2145                 return True;
2146             } 
2147             (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
2148             return True;
2149         }
2150         is->unused += count;
2151         p = is->buf;
2152         /* break input into lines and call the callback function on each
2153          * line
2154          */
2155         while (p < is->unused) {
2156             q = memchr(p, '\n', is->unused - p);
2157             if (q == NULL) break;
2158             q++;
2159             (is->func)(is, is->closure, p, q - p, 0);
2160             p = q;
2161         }
2162         /* remember not yet used part of the buffer */
2163         q = is->buf;
2164         while (p < is->unused) {
2165             *q++ = *p++;
2166         }
2167         is->unused = q;
2168     } else {
2169       /* read maximum length of input buffer and send the whole buffer
2170        * to the callback function
2171        */
2172         count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2173         if (count == -1)
2174           error = errno;
2175         else
2176           error = 0;
2177         (is->func)(is, is->closure, is->buf, count, error);
2178     }
2179     return True; // Must return true or the watch will be removed
2180 }
2181
2182 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2183      ProcRef pr;
2184      int lineByLine;
2185      InputCallback func;
2186      VOIDSTAR closure;
2187 {
2188     InputSource *is;
2189     GIOChannel *channel;
2190     ChildProc *cp = (ChildProc *) pr;
2191
2192     is = (InputSource *) calloc(1, sizeof(InputSource));
2193     is->lineByLine = lineByLine;
2194     is->func = func;
2195     if (pr == NoProc) {
2196         is->kind = CPReal;
2197         is->fd = fileno(stdin);
2198     } else {
2199         is->kind = cp->kind;
2200         is->fd = cp->fdFrom;
2201     }
2202     if (lineByLine)
2203       is->unused = is->buf;
2204     else
2205       is->unused = NULL;
2206
2207    /* GTK-TODO: will this work on windows?*/
2208
2209     channel = g_io_channel_unix_new(is->fd);
2210     g_io_channel_set_close_on_unref (channel, TRUE);
2211     is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2212
2213     is->closure = closure;
2214     return (InputSourceRef) is;
2215 }
2216
2217
2218 void
2219 RemoveInputSource(isr)
2220      InputSourceRef isr;
2221 {
2222     InputSource *is = (InputSource *) isr;
2223
2224     if (is->sid == 0) return;
2225     g_source_remove(is->sid);
2226     is->sid = 0;
2227     return;
2228 }
2229
2230 #ifndef HAVE_USLEEP
2231
2232 static Boolean frameWaiting;
2233
2234 static RETSIGTYPE
2235 FrameAlarm (int sig)
2236 {
2237   frameWaiting = False;
2238   /* In case System-V style signals.  Needed?? */
2239   signal(SIGALRM, FrameAlarm);
2240 }
2241
2242 void
2243 FrameDelay (int time)
2244 {
2245   struct itimerval delay;
2246
2247   if (time > 0) {
2248     frameWaiting = True;
2249     signal(SIGALRM, FrameAlarm);
2250     delay.it_interval.tv_sec =
2251       delay.it_value.tv_sec = time / 1000;
2252     delay.it_interval.tv_usec =
2253       delay.it_value.tv_usec = (time % 1000) * 1000;
2254     setitimer(ITIMER_REAL, &delay, NULL);
2255     while (frameWaiting) pause();
2256     delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2257     delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2258     setitimer(ITIMER_REAL, &delay, NULL);
2259   }
2260 }
2261
2262 #else
2263
2264 void
2265 FrameDelay (int time)
2266 {
2267 #ifdef TODO_GTK
2268   XSync(xDisplay, False);
2269 #endif
2270 //  gtk_main_iteration_do(False);
2271
2272   if (time > 0)
2273     usleep(time * 1000);
2274 }
2275
2276 #endif
2277
2278 static int
2279 FindLogo (char *place, char *name, char *buf)
2280 {   // check if file exists in given place
2281     FILE *f;
2282     if(!place) return 0;
2283     snprintf(buf, MSG_SIZ, "%s/%s.png", place, name);
2284     if(*place && strcmp(place, ".") && (f = fopen(buf, "r")) ) {
2285         fclose(f);
2286         return 1;
2287     }
2288     return 0;
2289 }
2290
2291 static void
2292 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2293 {
2294     char buf[MSG_SIZ], *logoName = buf;
2295     if(appData.logo[n][0]) {
2296         logoName = appData.logo[n];
2297     } else if(appData.autoLogo) {
2298         if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2299             sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2300         } else { // engine; cascade
2301             if(!FindLogo(appData.logoDir, cps->tidy, buf) &&   // first try user log folder
2302                !FindLogo(appData.directory[n], "logo", buf) && // then engine directory
2303                !FindLogo("/usr/local/share/games/plugins/logos", cps->tidy, buf) ) // then system folders
2304                 FindLogo("/usr/share/games/plugins/logos", cps->tidy, buf);
2305         }
2306     }
2307     if(logoName[0])
2308         { ASSIGN(cps->programLogo, logoName); }
2309 }
2310
2311 void
2312 UpdateLogos (int displ)
2313 {
2314     if(optList[W_WHITE-1].handle == NULL) return;
2315     LoadLogo(&first, 0, 0);
2316     LoadLogo(&second, 1, appData.icsActive);
2317     if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2318     return;
2319 }
2320
2321 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2322      char *label;
2323      char *def;
2324      char *filter;
2325      FileProc proc;
2326      char *openMode;
2327      Boolean pathFlag;
2328      char **name;
2329      FILE **fp;
2330 {
2331   GtkWidget     *dialog;
2332   GtkFileFilter *gtkfilter;
2333   GtkFileFilter *gtkfilter_all;
2334   char space[]     = " ";
2335   char fileext[10] = "";
2336   char *result     = NULL;
2337   char *cp;
2338   char curDir[MSG_SIZ];
2339
2340   StartDir(filter, NULL); // change to start directory for this file type
2341
2342   if(def && *def && def[strlen(def)-1] == '/') {
2343     getcwd(curDir, MSG_SIZ);
2344     chdir(def);
2345   }
2346
2347
2348   /* make a copy of the filter string, so that strtok can work with it*/
2349   cp = strdup(filter);
2350
2351   /* add filters for file extensions */
2352   gtkfilter     = gtk_file_filter_new();
2353   gtkfilter_all = gtk_file_filter_new();
2354
2355   /* one filter to show everything */
2356   gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2357   gtk_file_filter_set_name   (gtkfilter_all, "All Files");
2358
2359   /* add filter if present */
2360   result = strtok(cp, space);
2361   while( result != NULL  ) {
2362     snprintf(fileext,10,"*%s",result);
2363     result = strtok( NULL, space );
2364     gtk_file_filter_add_pattern(gtkfilter, fileext);
2365   };
2366
2367   /* second filter to only show what's useful */
2368   gtk_file_filter_set_name (gtkfilter,filter);
2369
2370   if (openMode[0] == 'r')
2371     {
2372       dialog = gtk_file_chooser_dialog_new (label,
2373                                             NULL,
2374                                             GTK_FILE_CHOOSER_ACTION_OPEN,
2375                                             GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2376                                             GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2377                                             NULL);
2378     }
2379   else
2380     {
2381       dialog = gtk_file_chooser_dialog_new (label,
2382                                             NULL,
2383                                             GTK_FILE_CHOOSER_ACTION_SAVE,
2384                                             GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2385                                             GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2386                                             NULL);
2387       /* add filename suggestions */
2388       if (strlen(def) > 0 )
2389         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2390
2391       //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2392     }
2393
2394   /* add filters */
2395   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2396   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2397   /* activate filter */
2398   gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2399
2400   if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2401     {
2402       char *filename;
2403       FILE *f;
2404
2405       filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2406
2407       //see loadgamepopup
2408       f = fopen(filename, openMode);
2409       if (f == NULL)
2410         {
2411           DisplayError(_("Failed to open file"), errno);
2412         }
2413       else
2414         {
2415           /* TODO add indec */
2416             *fp = f;
2417             ASSIGN(*name, filename);
2418             ScheduleDelayedEvent(DelayedLoad, 50);
2419         }
2420       StartDir(filter, filename);
2421       g_free (filename);
2422     }
2423   else StartDir(filter, "");
2424
2425   gtk_widget_destroy (dialog);
2426   ModeHighlight();
2427
2428   if(def && *def && def[strlen(def)-1] == '/') chdir(curDir);
2429
2430   free(cp);
2431   return;
2432
2433 }