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