Use dataDir/manDir variables always
[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 }
1701
1702 static guint delayedDragTag = 0;
1703
1704 void
1705 DragProc ()
1706 {
1707         static int busy;
1708         if(busy) return;
1709
1710         busy = 1;
1711         GetActualPlacement(shellWidget, &wpNew);
1712         if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1713            wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1714             busy = 0; return; // false alarm
1715         }
1716         ReSize(&wpNew);
1717         if(appData.useStickyWindows) {
1718             if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1719             if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1720             if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1721             if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1722             if(shellUp[ChatDlg]) CoDrag(shells[ChatDlg], &wpConsole);
1723         }
1724         wpMain = wpNew;
1725         DrawPosition(True, NULL);
1726         if(delayedDragTag) g_source_remove(delayedDragTag);
1727         delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1728         busy = 0;
1729 }
1730
1731 void
1732 DelayedDrag ()
1733 {
1734 //printf("old timr = %d\n", delayedDragTag);
1735     if(delayedDragTag) g_source_remove(delayedDragTag);
1736     delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1737 //printf("new timr = %d\n", delayedDragTag);
1738 }
1739
1740 static gboolean
1741 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1742 {
1743 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1744     // immediately
1745     wpNew.x = event->configure.x;
1746     wpNew.y = event->configure.y;
1747     wpNew.width  = event->configure.width;
1748     wpNew.height = event->configure.height;
1749     DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1750     return FALSE;
1751 }
1752
1753
1754
1755 /* Disable all user input other than deleting the window */
1756 static int frozen = 0;
1757
1758 void
1759 FreezeUI ()
1760 {
1761   if (frozen) return;
1762   /* Grab by a widget that doesn't accept input */
1763   gtk_grab_add(optList[W_MESSG].handle);
1764   frozen = 1;
1765 }
1766
1767 /* Undo a FreezeUI */
1768 void
1769 ThawUI ()
1770 {
1771   if (!frozen) return;
1772   gtk_grab_remove(optList[W_MESSG].handle);
1773   frozen = 0;
1774 }
1775
1776 void
1777 ModeHighlight ()
1778 {
1779     static int oldPausing = FALSE;
1780     static GameMode oldMode = (GameMode) -1;
1781     char *wname;
1782     if (!boardWidget) return;
1783
1784     if (pausing != oldPausing) {
1785         oldPausing = pausing;
1786         MarkMenuItem("Mode.Pause", pausing);
1787
1788         if (appData.showButtonBar) {
1789           /* Always toggle, don't set.  Previous code messes up when
1790              invoked while the button is pressed, as releasing it
1791              toggles the state again. */
1792             GdkColor color;
1793             gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1794             gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1795         }
1796     }
1797
1798     wname = ModeToWidgetName(oldMode);
1799     if (wname != NULL) {
1800         MarkMenuItem(wname, False);
1801     }
1802     wname = ModeToWidgetName(gameMode);
1803     if (wname != NULL) {
1804         MarkMenuItem(wname, True);
1805     }
1806     if(oldMode == TwoMachinesPlay) EnableNamedMenuItem("Mode.MachineMatch", True);
1807     MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1808     oldMode = gameMode;
1809
1810     /* Maybe all the enables should be handled here, not just this one */
1811     EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1812
1813     DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1814 }
1815
1816
1817 /*
1818  * Button/menu procedures
1819  */
1820
1821 void CopyFileToClipboard(gchar *filename)
1822 {
1823     gchar *selection_tmp;
1824     GtkClipboard *cb;
1825
1826     // read the file
1827     FILE* f = fopen(filename, "r");
1828     long len;
1829     size_t count;
1830     if (f == NULL) return;
1831     fseek(f, 0, 2);
1832     len = ftell(f);
1833     rewind(f);
1834     selection_tmp = g_try_malloc(len + 1);
1835     if (selection_tmp == NULL) {
1836         printf("Malloc failed in CopyFileToClipboard\n");
1837         return;
1838     }
1839     count = fread(selection_tmp, 1, len, f);
1840     fclose(f);
1841     if (len != count) {
1842       g_free(selection_tmp);
1843       return;
1844     }
1845     selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1846
1847     // copy selection_tmp to clipboard
1848     GdkDisplay *gdisp = gdk_display_get_default();
1849     if (!gdisp) {
1850         g_free(selection_tmp);
1851         return;
1852     }
1853     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1854     gtk_clipboard_set_text(cb, selection_tmp, -1);
1855     g_free(selection_tmp);
1856 }
1857
1858 void
1859 CopySomething (char *src)
1860 {
1861     GdkDisplay *gdisp = gdk_display_get_default();
1862     GtkClipboard *cb;
1863     if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1864     if (gdisp == NULL) return;
1865     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1866     gtk_clipboard_set_text(cb, src, -1);
1867 }
1868
1869 void
1870 PastePositionProc ()
1871 {
1872     GdkDisplay *gdisp = gdk_display_get_default();
1873     GtkClipboard *cb;
1874     gchar *fenstr;
1875
1876     if (gdisp == NULL) return;
1877     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1878     fenstr = gtk_clipboard_wait_for_text(cb);
1879     if (fenstr==NULL) return; // nothing had been selected to copy
1880     EditPositionPasteFEN(fenstr);
1881     return;
1882 }
1883
1884 void
1885 PasteGameProc ()
1886 {
1887     gchar *text=NULL;
1888     GtkClipboard *cb;
1889     guint len=0; int flip = appData.flipView;
1890     FILE* f;
1891
1892     // get game from clipboard
1893     GdkDisplay *gdisp = gdk_display_get_default();
1894     if (gdisp == NULL) return;
1895     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1896     text = gtk_clipboard_wait_for_text(cb);
1897     if (text == NULL) return; // nothing to paste
1898     len = strlen(text);
1899
1900     // write to temp file
1901     if (text == NULL || len == 0) {
1902       return; //nothing to paste
1903     }
1904     f = fopen(gamePasteFilename, "w");
1905     if (f == NULL) {
1906       DisplayError(_("Can't open temp file"), errno);
1907       return;
1908     }
1909     fwrite(text, 1, len, f);
1910     fclose(f);
1911
1912     // load from file
1913     if(!appData.autoFlipView) appData.flipView = flipView;
1914     LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1915     appData.flipView = flip;
1916     return;
1917 }
1918
1919
1920 #ifdef TODO_GTK
1921 void
1922 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1923 {
1924     QuitProc();
1925 }
1926 #endif
1927
1928 void MoveTypeInProc(eventkey)
1929     GdkEventKey  *eventkey;
1930 {
1931     char buf[10];
1932
1933     // ingnore if ctrl, alt, or meta is pressed
1934     if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1935         return;
1936     }
1937
1938     buf[0]=eventkey->keyval;
1939     buf[1]='\0';
1940     if (eventkey->keyval > 32 && eventkey->keyval < 256 || *buf == 27)
1941         ConsoleAutoPopUp (buf);
1942 }
1943
1944 #ifdef TODO_GTK
1945 void
1946 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1947 {
1948         if (!TempBackwardActive) {
1949                 TempBackwardActive = True;
1950                 BackwardEvent();
1951         }
1952 }
1953
1954 void
1955 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1956 {
1957         /* Check to see if triggered by a key release event for a repeating key.
1958          * If so the next queued event will be a key press of the same key at the same time */
1959         if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1960                 XEvent next;
1961                 XPeekEvent(xDisplay, &next);
1962                 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1963                         next.xkey.keycode == event->xkey.keycode)
1964                                 return;
1965         }
1966     ForwardEvent();
1967         TempBackwardActive = False;
1968 }
1969 #endif
1970
1971 void
1972 ManProc ()
1973 {   // called from menu
1974 #ifdef OSXAPP
1975     char buf[MSG_SIZ];
1976     snprintf(buf, MSG_SIZ, "osascript -e 'tell application \"Terminal\"' -e 'activate' -e 'do script \"man %s/../man/man6/xboard.6\"' -e 'end tell'", dataDir);
1977     system(buf);
1978 #else
1979     system("xterm -e man xboard &");
1980 #endif
1981 }
1982
1983 void
1984 InfoProc ()
1985 {
1986     char buf[MSG_SIZ];
1987 #ifdef OSXAPP
1988     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);
1989 #else
1990         snprintf(buf, sizeof(buf), "xterm -e info --directory %s --directory . -f %s &",
1991                  INFODIR, INFOFILE);
1992 #endif
1993     system(buf);
1994 }
1995
1996
1997 void
1998 SetWindowTitle (char *text, char *title, char *icon)
1999 {
2000 #ifdef TODO_GTK
2001     Arg args[16];
2002     int i;
2003     if (appData.titleInWindow) {
2004         i = 0;
2005         XtSetArg(args[i], XtNlabel, text);   i++;
2006         XtSetValues(titleWidget, args, i);
2007     }
2008     i = 0;
2009     XtSetArg(args[i], XtNiconName, (XtArgVal) icon);    i++;
2010     XtSetArg(args[i], XtNtitle, (XtArgVal) title);      i++;
2011     XtSetValues(shellWidget, args, i);
2012     XSync(xDisplay, False);
2013 #endif
2014     if (appData.titleInWindow) {
2015         SetWidgetLabel(titleWidget, text);
2016     }
2017     gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
2018 }
2019
2020
2021 void
2022 DisplayIcsInteractionTitle (String message)
2023 {
2024 #ifdef TODO_GTK
2025   if (oldICSInteractionTitle == NULL) {
2026     /* Magic to find the old window title, adapted from vim */
2027     char *wina = getenv("WINDOWID");
2028     if (wina != NULL) {
2029       Window win = (Window) atoi(wina);
2030       Window root, parent, *children;
2031       unsigned int nchildren;
2032       int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
2033       for (;;) {
2034         if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
2035         if (!XQueryTree(xDisplay, win, &root, &parent,
2036                         &children, &nchildren)) break;
2037         if (children) XFree((void *)children);
2038         if (parent == root || parent == 0) break;
2039         win = parent;
2040       }
2041       XSetErrorHandler(oldHandler);
2042     }
2043     if (oldICSInteractionTitle == NULL) {
2044       oldICSInteractionTitle = "xterm";
2045     }
2046   }
2047   printf("\033]0;%s\007", message);
2048   fflush(stdout);
2049 #endif
2050 }
2051
2052
2053 void
2054 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
2055 {
2056     GtkWidget *w = (GtkWidget *) opt->handle;
2057     GdkColor col;
2058     char *markup;
2059     char bgcolor[10];
2060     char fgcolor[10];
2061
2062     if (highlight) {
2063         strcpy(bgcolor, "black");
2064         strcpy(fgcolor, "white");
2065     } else {
2066         strcpy(bgcolor, "white");
2067         strcpy(fgcolor, "black");
2068     }
2069     if (timer > 0 &&
2070         appData.lowTimeWarning &&
2071         (timer / 1000) < appData.icsAlarmTime) {
2072         strcpy(fgcolor, appData.lowTimeWarningColor);
2073     }
2074
2075     gdk_color_parse( bgcolor, &col );
2076     gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
2077
2078     if (appData.clockMode) {
2079         markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>", appData.clockFont,
2080                                          bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2081 //        markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
2082 //                                       bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2083     } else {
2084         markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s  </span>", appData.clockFont,
2085                                          bgcolor, fgcolor, color);
2086 //        markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s  </span>",
2087 //                                       bgcolor, fgcolor, color);
2088     }
2089     gtk_label_set_markup(GTK_LABEL(w), markup);
2090     g_free(markup);
2091 }
2092
2093 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
2094
2095 void
2096 SetClockIcon (int color)
2097 {
2098     GdkPixbuf *pm = *clockIcons[color];
2099     if (mainwindowIcon != pm) {
2100         mainwindowIcon = pm;
2101 #ifdef OSXAPP
2102         gtkosx_application_set_dock_icon_pixbuf(theApp, mainwindowIcon);
2103 #else
2104         gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
2105 #endif
2106     }
2107 }
2108
2109 #define INPUT_SOURCE_BUF_SIZE 8192
2110
2111 typedef struct {
2112     CPKind kind;
2113     int fd;
2114     int lineByLine;
2115     char *unused;
2116     InputCallback func;
2117     guint sid;
2118     char buf[INPUT_SOURCE_BUF_SIZE];
2119     VOIDSTAR closure;
2120 } InputSource;
2121
2122 gboolean
2123 DoInputCallback(io, cond, data)
2124      GIOChannel  *io;
2125      GIOCondition cond;
2126      gpointer    *data;
2127 {
2128   /* read input from one of the input source (for example a chess program, ICS, etc).
2129    * and call a function that will handle the input
2130    */
2131
2132     int count;
2133     int error;
2134     char *p, *q;
2135
2136     /* All information (callback function, file descriptor, etc) is
2137      * saved in an InputSource structure
2138      */
2139     InputSource *is = (InputSource *) data;
2140
2141     if (is->lineByLine) {
2142         count = read(is->fd, is->unused,
2143                      INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
2144         if (count <= 0) {
2145             if(count == 0 && is->kind == CPReal && shells[ChatDlg]) { // [HGM] absence of terminal is no error if ICS Console present
2146                 RemoveInputSource(is); // cease reading stdin
2147                 stdoutClosed = TRUE;   // suppress future output
2148                 return True;
2149             } 
2150             (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
2151             return True;
2152         }
2153         is->unused += count;
2154         p = is->buf;
2155         /* break input into lines and call the callback function on each
2156          * line
2157          */
2158         while (p < is->unused) {
2159             q = memchr(p, '\n', is->unused - p);
2160             if (q == NULL) break;
2161             q++;
2162             (is->func)(is, is->closure, p, q - p, 0);
2163             p = q;
2164         }
2165         /* remember not yet used part of the buffer */
2166         q = is->buf;
2167         while (p < is->unused) {
2168             *q++ = *p++;
2169         }
2170         is->unused = q;
2171     } else {
2172       /* read maximum length of input buffer and send the whole buffer
2173        * to the callback function
2174        */
2175         count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2176         if (count == -1)
2177           error = errno;
2178         else
2179           error = 0;
2180         (is->func)(is, is->closure, is->buf, count, error);
2181     }
2182     return True; // Must return true or the watch will be removed
2183 }
2184
2185 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2186      ProcRef pr;
2187      int lineByLine;
2188      InputCallback func;
2189      VOIDSTAR closure;
2190 {
2191     InputSource *is;
2192     GIOChannel *channel;
2193     ChildProc *cp = (ChildProc *) pr;
2194
2195     is = (InputSource *) calloc(1, sizeof(InputSource));
2196     is->lineByLine = lineByLine;
2197     is->func = func;
2198     if (pr == NoProc) {
2199         is->kind = CPReal;
2200         is->fd = fileno(stdin);
2201     } else {
2202         is->kind = cp->kind;
2203         is->fd = cp->fdFrom;
2204     }
2205     if (lineByLine)
2206       is->unused = is->buf;
2207     else
2208       is->unused = NULL;
2209
2210    /* GTK-TODO: will this work on windows?*/
2211
2212     channel = g_io_channel_unix_new(is->fd);
2213     g_io_channel_set_close_on_unref (channel, TRUE);
2214     is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2215
2216     is->closure = closure;
2217     return (InputSourceRef) is;
2218 }
2219
2220
2221 void
2222 RemoveInputSource(isr)
2223      InputSourceRef isr;
2224 {
2225     InputSource *is = (InputSource *) isr;
2226
2227     if (is->sid == 0) return;
2228     g_source_remove(is->sid);
2229     is->sid = 0;
2230     return;
2231 }
2232
2233 #ifndef HAVE_USLEEP
2234
2235 static Boolean frameWaiting;
2236
2237 static RETSIGTYPE
2238 FrameAlarm (int sig)
2239 {
2240   frameWaiting = False;
2241   /* In case System-V style signals.  Needed?? */
2242   signal(SIGALRM, FrameAlarm);
2243 }
2244
2245 void
2246 FrameDelay (int time)
2247 {
2248   struct itimerval delay;
2249
2250   if (time > 0) {
2251     frameWaiting = True;
2252     signal(SIGALRM, FrameAlarm);
2253     delay.it_interval.tv_sec =
2254       delay.it_value.tv_sec = time / 1000;
2255     delay.it_interval.tv_usec =
2256       delay.it_value.tv_usec = (time % 1000) * 1000;
2257     setitimer(ITIMER_REAL, &delay, NULL);
2258     while (frameWaiting) pause();
2259     delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2260     delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2261     setitimer(ITIMER_REAL, &delay, NULL);
2262   }
2263 }
2264
2265 #else
2266
2267 void
2268 FrameDelay (int time)
2269 {
2270 #ifdef TODO_GTK
2271   XSync(xDisplay, False);
2272 #endif
2273 //  gtk_main_iteration_do(False);
2274
2275   if (time > 0)
2276     usleep(time * 1000);
2277 }
2278
2279 #endif
2280
2281 static int
2282 FindLogo (char *place, char *name, char *buf)
2283 {   // check if file exists in given place
2284     FILE *f;
2285     if(!place) return 0;
2286     snprintf(buf, MSG_SIZ, "%s/%s.png", place, name);
2287     if(*place && strcmp(place, ".") && (f = fopen(buf, "r")) ) {
2288         fclose(f);
2289         return 1;
2290     }
2291     return 0;
2292 }
2293
2294 static void
2295 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2296 {
2297     char buf[MSG_SIZ], *logoName = buf;
2298     if(appData.logo[n][0]) {
2299         logoName = appData.logo[n];
2300     } else if(appData.autoLogo) {
2301         if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2302             sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2303         } else { // engine; cascade
2304             if(!FindLogo(appData.logoDir, cps->tidy, buf) &&   // first try user log folder
2305                !FindLogo(appData.directory[n], "logo", buf) && // then engine directory
2306                !FindLogo("/usr/local/share/games/plugins/logos", cps->tidy, buf) ) // then system folders
2307                 FindLogo("/usr/share/games/plugins/logos", cps->tidy, buf);
2308         }
2309     }
2310     if(logoName[0])
2311         { ASSIGN(cps->programLogo, logoName); }
2312 }
2313
2314 void
2315 UpdateLogos (int displ)
2316 {
2317     if(optList[W_WHITE-1].handle == NULL) return;
2318     LoadLogo(&first, 0, 0);
2319     LoadLogo(&second, 1, appData.icsActive);
2320     if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2321     return;
2322 }
2323
2324 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2325      char *label;
2326      char *def;
2327      char *filter;
2328      FileProc proc;
2329      char *openMode;
2330      Boolean pathFlag;
2331      char **name;
2332      FILE **fp;
2333 {
2334   GtkWidget     *dialog;
2335   GtkFileFilter *gtkfilter;
2336   GtkFileFilter *gtkfilter_all;
2337   char space[]     = " ";
2338   char fileext[10] = "";
2339   char *result     = NULL;
2340   char *cp;
2341   char curDir[MSG_SIZ];
2342
2343   StartDir(filter, NULL); // change to start directory for this file type
2344
2345   if(def && *def && def[strlen(def)-1] == '/') {
2346     getcwd(curDir, MSG_SIZ);
2347     chdir(def);
2348   }
2349
2350
2351   /* make a copy of the filter string, so that strtok can work with it*/
2352   cp = strdup(filter);
2353
2354   /* add filters for file extensions */
2355   gtkfilter     = gtk_file_filter_new();
2356   gtkfilter_all = gtk_file_filter_new();
2357
2358   /* one filter to show everything */
2359   gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2360   gtk_file_filter_set_name   (gtkfilter_all, "All Files");
2361
2362   /* add filter if present */
2363   result = strtok(cp, space);
2364   while( result != NULL  ) {
2365     snprintf(fileext,10,"*%s",result);
2366     result = strtok( NULL, space );
2367     gtk_file_filter_add_pattern(gtkfilter, fileext);
2368   };
2369
2370   /* second filter to only show what's useful */
2371   gtk_file_filter_set_name (gtkfilter,filter);
2372
2373   if (openMode[0] == 'r')
2374     {
2375       dialog = gtk_file_chooser_dialog_new (label,
2376                                             NULL,
2377                                             GTK_FILE_CHOOSER_ACTION_OPEN,
2378                                             GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2379                                             GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2380                                             NULL);
2381     }
2382   else
2383     {
2384       dialog = gtk_file_chooser_dialog_new (label,
2385                                             NULL,
2386                                             GTK_FILE_CHOOSER_ACTION_SAVE,
2387                                             GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2388                                             GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2389                                             NULL);
2390       /* add filename suggestions */
2391       if (strlen(def) > 0 )
2392         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2393
2394       //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2395     }
2396
2397   /* add filters */
2398   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2399   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2400   /* activate filter */
2401   gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2402
2403   if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2404     {
2405       char *filename;
2406       FILE *f;
2407
2408       filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2409
2410       //see loadgamepopup
2411       f = fopen(filename, openMode);
2412       if (f == NULL)
2413         {
2414           DisplayError(_("Failed to open file"), errno);
2415         }
2416       else
2417         {
2418           /* TODO add indec */
2419             *fp = f;
2420             ASSIGN(*name, filename);
2421             ScheduleDelayedEvent(DelayedLoad, 50);
2422         }
2423       StartDir(filter, filename);
2424       g_free (filename);
2425     }
2426   else StartDir(filter, "");
2427
2428   gtk_widget_destroy (dialog);
2429   ModeHighlight();
2430
2431   if(def && *def && def[strlen(def)-1] == '/') chdir(curDir);
2432
2433   free(cp);
2434   return;
2435
2436 }