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