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