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