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