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