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