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