Force GTK logo size to quarter board width
[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     if(appData.logoSize) appData.logoSize = boardWidth/4-3;
1115     wpMain.width = -1; // prevent popup sizes window
1116     optList = BoardPopUp(squareSize, lineGap, (void*)
1117 #ifdef TODO_GTK
1118 #if ENABLE_NLS
1119                                                 &clockFontSet);
1120 #else
1121                                                 clockFontStruct);
1122 #endif
1123 #else
1124 0);
1125 #endif
1126     InitDrawingHandle(optList + W_BOARD);
1127     shellWidget      = shells[BoardWindow];
1128     currBoard        = &optList[W_BOARD];
1129     boardWidget      = optList[W_BOARD].handle;
1130     menuBarWidget    = optList[W_MENU].handle;
1131     dropMenu         = optList[W_DROP].handle;
1132     titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1133 #ifdef TODO_GTK
1134     formWidget  = XtParent(boardWidget);
1135     XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1136     XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1137     XtGetValues(optList[W_WHITE].handle, args, 2);
1138     if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1139       XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1140       XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1141       XtGetValues(optList[W_PAUSE].handle, args, 2);
1142     }
1143 #endif
1144
1145     // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1146     //       not need to go into InitDrawingSizes().
1147
1148     InitMenuMarkers();
1149
1150     // add accelerators to main shell
1151     gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1152
1153     /*
1154      * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1155      */
1156     WhiteIcon  = LoadIconFile("icon_white");
1157     BlackIcon  = LoadIconFile("icon_black");
1158     SetClockIcon(0); // sets white icon
1159
1160
1161     /*
1162      * Create a cursor for the board widget.
1163      */
1164 #ifdef TODO_GTK
1165     window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1166     XChangeWindowAttributes(xDisplay, xBoardWindow,
1167                             CWCursor, &window_attributes);
1168 #endif
1169
1170     /*
1171      * Inhibit shell resizing.
1172      */
1173 #ifdef TODO_GTK
1174     shellArgs[0].value = (XtArgVal) &w;
1175     shellArgs[1].value = (XtArgVal) &h;
1176     XtGetValues(shellWidget, shellArgs, 2);
1177     shellArgs[4].value = shellArgs[2].value = w;
1178     shellArgs[5].value = shellArgs[3].value = h;
1179 //    XtSetValues(shellWidget, &shellArgs[2], 4);
1180 #endif
1181     {
1182         // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1183         // It wil only become known asynchronously, when we first write a string into it.
1184         // This will then change the clock widget height, which triggers resizing the top-level window
1185         // and a configure event. Only then can we know the total height of the top-level window,
1186         // and calculate the height we need. The clockKludge flag suppresses all resizing until
1187         // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1188         int hc;
1189         GtkAllocation a;
1190         gtk_widget_get_allocation(shells[BoardWindow], &a);
1191         w = a.width; h = a.height;
1192         gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1193         clockKludge = hc = a.height;
1194         gtk_widget_get_allocation(boardWidget, &a);
1195 //      marginW =  w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1196         marginH =  h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1197     }
1198
1199     CreateAnyPieces(1);
1200     CreateGrid();
1201
1202     if(appData.logoSize)
1203     {   // locate and read user logo
1204         char buf[MSG_SIZ];
1205         snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1206         ASSIGN(userLogo, buf);
1207     }
1208
1209     if (appData.animate || appData.animateDragging)
1210       CreateAnimVars();
1211
1212     g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1213     g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1214
1215     /* [AS] Restore layout */
1216     if( wpMoveHistory.visible ) {
1217       HistoryPopUp();
1218     }
1219
1220     if( wpEvalGraph.visible )
1221       {
1222         EvalGraphPopUp();
1223       };
1224
1225     if( wpEngineOutput.visible ) {
1226       EngineOutputPopUp();
1227     }
1228
1229     if( wpConsole.visible && appData.icsActive ) {
1230       ChatProc();
1231       BoardToTop();
1232     }
1233
1234     gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1235     InitPosition(TRUE);
1236
1237     InitBackEnd2();
1238
1239     if (errorExitStatus == -1) {
1240         if (appData.icsActive) {
1241             /* We now wait until we see "login:" from the ICS before
1242                sending the logon script (problems with timestamp otherwise) */
1243             /*ICSInitScript();*/
1244             if (appData.icsInputBox) ICSInputBoxPopUp();
1245         }
1246
1247     #ifdef SIGWINCH
1248     signal(SIGWINCH, TermSizeSigHandler);
1249     #endif
1250         signal(SIGINT, IntSigHandler);
1251         signal(SIGTERM, IntSigHandler);
1252         if (*appData.cmailGameName != NULLCHAR) {
1253             signal(SIGUSR1, CmailSigHandler);
1254         }
1255     }
1256
1257     UpdateLogos(TRUE);
1258 //    XtSetKeyboardFocus(shellWidget, formWidget);
1259 #ifdef TODO_GTK
1260     XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1261 #endif
1262
1263     /* check for GTK events and process them */
1264 //    gtk_main();
1265 while(1) {
1266 gtk_main_iteration();
1267 }
1268
1269     if (appData.debugMode) fclose(debugFP); // [DM] debug
1270     return 0;
1271 }
1272
1273 void
1274 DoEvents ()
1275 {
1276     while(gtk_events_pending()) gtk_main_iteration();
1277 }
1278
1279 RETSIGTYPE
1280 TermSizeSigHandler (int sig)
1281 {
1282     update_ics_width();
1283 }
1284
1285 RETSIGTYPE
1286 IntSigHandler (int sig)
1287 {
1288     ExitEvent(sig);
1289 }
1290
1291 RETSIGTYPE
1292 CmailSigHandler (int sig)
1293 {
1294     int dummy = 0;
1295     int error;
1296
1297     signal(SIGUSR1, SIG_IGN);   /* suspend handler     */
1298
1299     /* Activate call-back function CmailSigHandlerCallBack()             */
1300     OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1301
1302     signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1303 }
1304
1305 void
1306 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1307 {
1308     BoardToTop();
1309     ReloadCmailMsgEvent(TRUE);  /* Reload cmail msg  */
1310 }
1311 /**** end signal code ****/
1312
1313
1314 #define Abs(n) ((n)<0 ? -(n) : (n))
1315
1316 char *
1317 InsertPxlSize (char *pattern, int targetPxlSize)
1318 {
1319     char buf[MSG_SIZ];
1320     snprintf(buf, MSG_SIZ, pattern, targetPxlSize); // pattern is something like "Sans Bold %d"
1321     return strdup(buf);
1322 }
1323
1324 #ifdef ENABLE_NLS
1325 #ifdef TODO_GTK
1326 char *
1327 InsertPxlSize (char *pattern, int targetPxlSize)
1328 {
1329     char *base_fnt_lst, strInt[12], *p, *q;
1330     int alternatives, i, len, strIntLen;
1331
1332     /*
1333      * Replace the "*" (if present) in the pixel-size slot of each
1334      * alternative with the targetPxlSize.
1335      */
1336     p = pattern;
1337     alternatives = 1;
1338     while ((p = strchr(p, ',')) != NULL) {
1339       alternatives++;
1340       p++;
1341     }
1342     snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1343     strIntLen = strlen(strInt);
1344     base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1345
1346     p = pattern;
1347     q = base_fnt_lst;
1348     while (alternatives--) {
1349       char *comma = strchr(p, ',');
1350       for (i=0; i<14; i++) {
1351         char *hyphen = strchr(p, '-');
1352         if (!hyphen) break;
1353         if (comma && hyphen > comma) break;
1354         len = hyphen + 1 - p;
1355         if (i == 7 && *p == '*' && len == 2) {
1356           p += len;
1357           memcpy(q, strInt, strIntLen);
1358           q += strIntLen;
1359           *q++ = '-';
1360         } else {
1361           memcpy(q, p, len);
1362           p += len;
1363           q += len;
1364         }
1365       }
1366       if (!comma) break;
1367       len = comma + 1 - p;
1368       memcpy(q, p, len);
1369       p += len;
1370       q += len;
1371     }
1372     strcpy(q, p);
1373
1374     return base_fnt_lst;
1375 }
1376 #endif
1377
1378 #ifdef TODO_GTK
1379 XFontSet
1380 CreateFontSet (char *base_fnt_lst)
1381 {
1382     XFontSet fntSet;
1383     char **missing_list;
1384     int missing_count;
1385     char *def_string;
1386
1387     fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1388                             &missing_list, &missing_count, &def_string);
1389     if (appData.debugMode) {
1390       int i, count;
1391       XFontStruct **font_struct_list;
1392       char **font_name_list;
1393       fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1394       if (fntSet) {
1395         fprintf(debugFP, " got list %s, locale %s\n",
1396                 XBaseFontNameListOfFontSet(fntSet),
1397                 XLocaleOfFontSet(fntSet));
1398         count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1399         for (i = 0; i < count; i++) {
1400           fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1401         }
1402       }
1403       for (i = 0; i < missing_count; i++) {
1404         fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1405       }
1406     }
1407     if (fntSet == NULL) {
1408       fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1409       exit(2);
1410     }
1411     return fntSet;
1412 }
1413 #endif
1414 #else // not ENABLE_NLS
1415 /*
1416  * Find a font that matches "pattern" that is as close as
1417  * possible to the targetPxlSize.  Prefer fonts that are k
1418  * pixels smaller to fonts that are k pixels larger.  The
1419  * pattern must be in the X Consortium standard format,
1420  * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1421  * The return value should be freed with XtFree when no
1422  * longer needed.
1423  */
1424 #ifdef TODO_GTK
1425 char *
1426 FindFont (char *pattern, int targetPxlSize)
1427 {
1428     char **fonts, *p, *best, *scalable, *scalableTail;
1429     int i, j, nfonts, minerr, err, pxlSize;
1430
1431     fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1432     if (nfonts < 1) {
1433         fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1434                 programName, pattern);
1435         exit(2);
1436     }
1437
1438     best = fonts[0];
1439     scalable = NULL;
1440     minerr = 999999;
1441     for (i=0; i<nfonts; i++) {
1442         j = 0;
1443         p = fonts[i];
1444         if (*p != '-') continue;
1445         while (j < 7) {
1446             if (*p == NULLCHAR) break;
1447             if (*p++ == '-') j++;
1448         }
1449         if (j < 7) continue;
1450         pxlSize = atoi(p);
1451         if (pxlSize == 0) {
1452             scalable = fonts[i];
1453             scalableTail = p;
1454         } else {
1455             err = pxlSize - targetPxlSize;
1456             if (Abs(err) < Abs(minerr) ||
1457                 (minerr > 0 && err < 0 && -err == minerr)) {
1458                 best = fonts[i];
1459                 minerr = err;
1460             }
1461         }
1462     }
1463     if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1464         /* If the error is too big and there is a scalable font,
1465            use the scalable font. */
1466         int headlen = scalableTail - scalable;
1467         p = (char *) XtMalloc(strlen(scalable) + 10);
1468         while (isdigit(*scalableTail)) scalableTail++;
1469         sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1470     } else {
1471         p = (char *) XtMalloc(strlen(best) + 2);
1472         safeStrCpy(p, best, strlen(best)+1 );
1473     }
1474     if (appData.debugMode) {
1475         fprintf(debugFP, "resolved %s at pixel size %d\n  to %s\n",
1476                 pattern, targetPxlSize, p);
1477     }
1478     XFreeFontNames(fonts);
1479     return p;
1480 }
1481 #endif
1482 #endif
1483
1484 void
1485 MarkMenuItem (char *menuRef, int state)
1486 {
1487     MenuItem *item = MenuNameToItem(menuRef);
1488
1489     if(item && item->handle) {
1490         ((GtkCheckMenuItem *) (item->handle))->active = state;
1491     }
1492     SYNC_MENUBAR;
1493 }
1494
1495 void
1496 EnableNamedMenuItem (char *menuRef, int state)
1497 {
1498     MenuItem *item = MenuNameToItem(menuRef);
1499
1500     if(item && item->handle) gtk_widget_set_sensitive(item->handle, state);
1501     SYNC_MENUBAR;
1502 }
1503
1504 void
1505 EnableButtonBar (int state)
1506 {
1507 #ifdef TODO_GTK
1508     XtSetSensitive(optList[W_BUTTON].handle, state);
1509 #endif
1510 }
1511
1512
1513 void
1514 SetMenuEnables (Enables *enab)
1515 {
1516   while (enab->name != NULL) {
1517     EnableNamedMenuItem(enab->name, enab->value);
1518     enab++;
1519   }
1520 }
1521
1522 gboolean KeyPressProc(window, eventkey, data)
1523      GtkWindow *window;
1524      GdkEventKey  *eventkey;
1525      gpointer data;
1526 {
1527
1528     MoveTypeInProc(eventkey); // pop up for typed in moves
1529
1530 #ifdef TODO_GTK
1531     /* check for other key values */
1532     switch(eventkey->keyval) {
1533         case GDK_question:
1534           AboutGameEvent();
1535           break;
1536         default:
1537           break;
1538     }
1539 #endif
1540     return False;
1541 }
1542 #ifdef TODO_GTK
1543 void
1544 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1545 {   // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1546     MenuItem *item;
1547     if(*nprms == 0) return;
1548     item = MenuNameToItem(prms[0]);
1549     if(item) ((MenuProc *) item->proc) ();
1550 }
1551 #endif
1552
1553 void
1554 SetupDropMenu ()
1555 {
1556 #ifdef TODO_GTK
1557     int i, j, count;
1558     char label[32];
1559     Arg args[16];
1560     Widget entry;
1561     char* p;
1562
1563     for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1564         entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1565         p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1566                    dmEnables[i].piece);
1567         XtSetSensitive(entry, p != NULL || !appData.testLegality
1568                        /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1569                                        && !appData.icsActive));
1570         count = 0;
1571         while (p && *p++ == dmEnables[i].piece) count++;
1572         snprintf(label, sizeof(label), "%s  %d", dmEnables[i].widget, count);
1573         j = 0;
1574         XtSetArg(args[j], XtNlabel, label); j++;
1575         XtSetValues(entry, args, j);
1576     }
1577 #endif
1578 }
1579
1580 static void
1581 do_flash_delay (unsigned long msec)
1582 {
1583     TimeDelay(msec);
1584 }
1585
1586 void
1587 FlashDelay (int flash_delay)
1588 {
1589         if(flash_delay) do_flash_delay(flash_delay);
1590 }
1591
1592 double
1593 Fraction (int x, int start, int stop)
1594 {
1595    double f = ((double) x - start)/(stop - start);
1596    if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1597    return f;
1598 }
1599
1600 static WindowPlacement wpNew;
1601
1602 void
1603 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1604 {
1605     int touch=0, fudge = 4, f = 3;
1606     GetActualPlacement(sh, wp);
1607     if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x)         < fudge) touch = 1; else // right touch
1608     if(abs(wp->x + wp->width + 2*frameX - f - wpMain.x)            < fudge) touch = 2; else // left touch
1609     if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1610     if(abs(wp->y + wp->height + frameX + frameY - f - wpMain.y)    < fudge) touch = 4;      // top touch
1611 //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);
1612     if(!touch ) return; // only windows that touch co-move
1613     if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1614         int heightInc = wpNew.height - wpMain.height;
1615         double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1616         double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1617         wp->y += fracTop * heightInc;
1618         heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1619 #ifdef TODO_GTK
1620         if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1621 #endif
1622         wp->height += heightInc;
1623     } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1624         int widthInc = wpNew.width - wpMain.width;
1625         double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1626         double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1627         wp->y += fracLeft * widthInc;
1628         widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1629 #ifdef TODO_GTK
1630         if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1631 #endif
1632         wp->width += widthInc;
1633     }
1634     wp->x += wpNew.x - wpMain.x;
1635     wp->y += wpNew.y - wpMain.y;
1636     if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1637     if(touch == 3) wp->y += wpNew.height - wpMain.height;
1638 #ifdef TODO_GTK
1639     XtSetArg(args[j], XtNx, wp->x); j++;
1640     XtSetArg(args[j], XtNy, wp->y); j++;
1641     XtSetValues(sh, args, j);
1642 #endif
1643         gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1644 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1645         gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1646 }
1647
1648 void
1649 ReSize (WindowPlacement *wp)
1650 {
1651         GtkAllocation a;
1652         int sqx, sqy, w, h, hc, lg = lineGap;
1653         gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1654         hc = a.height; // clock height can depend on single / double line clock text!
1655         if(clockKludge && hc != clockKludge) wp->height += hc - clockKludge, clockKludge = 0;
1656         wpMain.height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1657         if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1658         sqx = (wp->width  - lg - marginW) / BOARD_WIDTH - lg;
1659         sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1660         if(sqy < sqx) sqx = sqy;
1661         if(sqx < 20) return;
1662         if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1663             int oldSqx = sqx;
1664             lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1665             sqx = (wp->width  - lg - marginW) / BOARD_WIDTH - lg;
1666             sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1667             if(sqy < sqx) sqx = sqy;
1668             lg = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1669             if(sqx == oldSqx + 1 && lg == lineGap + 1) sqx = oldSqx, squareSize = 0; // prevent oscillations, force resize by kludge
1670         }
1671         if(sqx != squareSize) {
1672             squareSize = sqx; // adopt new square size
1673             CreatePNGPieces(); // make newly scaled pieces
1674             InitDrawingSizes(0, 0); // creates grid etc.
1675         } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1676         w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1677         h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1678         if(optList[W_BOARD].max   > w) optList[W_BOARD].max = w;
1679         if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1680 }
1681
1682 static guint delayedDragTag = 0;
1683
1684 void
1685 DragProc ()
1686 {
1687         static int busy;
1688         if(busy) return;
1689
1690         busy = 1;
1691 //      GetActualPlacement(shellWidget, &wpNew);
1692         if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1693            wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1694             busy = 0; return; // false alarm
1695         }
1696         ReSize(&wpNew);
1697         if(appData.useStickyWindows) {
1698             if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1699             if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1700             if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1701             if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1702             if(shellUp[ChatDlg]) CoDrag(shells[ChatDlg], &wpConsole);
1703         }
1704         wpMain = wpNew;
1705         DrawPosition(True, NULL);
1706         if(delayedDragTag) g_source_remove(delayedDragTag);
1707         delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1708         busy = 0;
1709 }
1710
1711 void
1712 DelayedDrag ()
1713 {
1714 //printf("old timr = %d\n", delayedDragTag);
1715     if(delayedDragTag) g_source_remove(delayedDragTag);
1716     delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1717 //printf("new timr = %d\n", delayedDragTag);
1718 }
1719
1720 static gboolean
1721 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1722 {
1723 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1724     // immediately
1725     wpNew.x = event->configure.x;
1726     wpNew.y = event->configure.y;
1727     wpNew.width  = event->configure.width;
1728     wpNew.height = event->configure.height;
1729     DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1730     return FALSE;
1731 }
1732
1733
1734
1735 /* Disable all user input other than deleting the window */
1736 static int frozen = 0;
1737
1738 void
1739 FreezeUI ()
1740 {
1741   if (frozen) return;
1742   /* Grab by a widget that doesn't accept input */
1743   gtk_grab_add(optList[W_MESSG].handle);
1744   frozen = 1;
1745 }
1746
1747 /* Undo a FreezeUI */
1748 void
1749 ThawUI ()
1750 {
1751   if (!frozen) return;
1752   gtk_grab_remove(optList[W_MESSG].handle);
1753   frozen = 0;
1754 }
1755
1756 void
1757 ModeHighlight ()
1758 {
1759     static int oldPausing = FALSE;
1760     static GameMode oldmode = (GameMode) -1;
1761     char *wname;
1762     if (!boardWidget) return;
1763
1764     if (pausing != oldPausing) {
1765         oldPausing = pausing;
1766         MarkMenuItem("Mode.Pause", pausing);
1767
1768         if (appData.showButtonBar) {
1769           /* Always toggle, don't set.  Previous code messes up when
1770              invoked while the button is pressed, as releasing it
1771              toggles the state again. */
1772             GdkColor color;
1773             gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1774             gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1775         }
1776     }
1777
1778     wname = ModeToWidgetName(oldmode);
1779     if (wname != NULL) {
1780         MarkMenuItem(wname, False);
1781     }
1782     wname = ModeToWidgetName(gameMode);
1783     if (wname != NULL) {
1784         MarkMenuItem(wname, True);
1785     }
1786     oldmode = gameMode;
1787     MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1788
1789     /* Maybe all the enables should be handled here, not just this one */
1790     EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1791
1792     DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1793 }
1794
1795
1796 /*
1797  * Button/menu procedures
1798  */
1799
1800 void CopyFileToClipboard(gchar *filename)
1801 {
1802     gchar *selection_tmp;
1803     GtkClipboard *cb;
1804
1805     // read the file
1806     FILE* f = fopen(filename, "r");
1807     long len;
1808     size_t count;
1809     if (f == NULL) return;
1810     fseek(f, 0, 2);
1811     len = ftell(f);
1812     rewind(f);
1813     selection_tmp = g_try_malloc(len + 1);
1814     if (selection_tmp == NULL) {
1815         printf("Malloc failed in CopyFileToClipboard\n");
1816         return;
1817     }
1818     count = fread(selection_tmp, 1, len, f);
1819     fclose(f);
1820     if (len != count) {
1821       g_free(selection_tmp);
1822       return;
1823     }
1824     selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1825
1826     // copy selection_tmp to clipboard
1827     GdkDisplay *gdisp = gdk_display_get_default();
1828     if (!gdisp) {
1829         g_free(selection_tmp);
1830         return;
1831     }
1832     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1833     gtk_clipboard_set_text(cb, selection_tmp, -1);
1834     g_free(selection_tmp);
1835 }
1836
1837 void
1838 CopySomething (char *src)
1839 {
1840     GdkDisplay *gdisp = gdk_display_get_default();
1841     GtkClipboard *cb;
1842     if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1843     if (gdisp == NULL) return;
1844     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1845     gtk_clipboard_set_text(cb, src, -1);
1846 }
1847
1848 void
1849 PastePositionProc ()
1850 {
1851     GdkDisplay *gdisp = gdk_display_get_default();
1852     GtkClipboard *cb;
1853     gchar *fenstr;
1854
1855     if (gdisp == NULL) return;
1856     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1857     fenstr = gtk_clipboard_wait_for_text(cb);
1858     if (fenstr==NULL) return; // nothing had been selected to copy
1859     EditPositionPasteFEN(fenstr);
1860     return;
1861 }
1862
1863 void
1864 PasteGameProc ()
1865 {
1866     gchar *text=NULL;
1867     GtkClipboard *cb;
1868     guint len=0;
1869     FILE* f;
1870
1871     // get game from clipboard
1872     GdkDisplay *gdisp = gdk_display_get_default();
1873     if (gdisp == NULL) return;
1874     cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1875     text = gtk_clipboard_wait_for_text(cb);
1876     if (text == NULL) return; // nothing to paste
1877     len = strlen(text);
1878
1879     // write to temp file
1880     if (text == NULL || len == 0) {
1881       return; //nothing to paste
1882     }
1883     f = fopen(gamePasteFilename, "w");
1884     if (f == NULL) {
1885       DisplayError(_("Can't open temp file"), errno);
1886       return;
1887     }
1888     fwrite(text, 1, len, f);
1889     fclose(f);
1890
1891     // load from file
1892     LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1893     return;
1894 }
1895
1896
1897 #ifdef TODO_GTK
1898 void
1899 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1900 {
1901     QuitProc();
1902 }
1903 #endif
1904
1905 void MoveTypeInProc(eventkey)
1906     GdkEventKey  *eventkey;
1907 {
1908     char buf[10];
1909
1910     // ingnore if ctrl, alt, or meta is pressed
1911     if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1912         return;
1913     }
1914
1915     buf[0]=eventkey->keyval;
1916     buf[1]='\0';
1917     if (eventkey->keyval > 32 && eventkey->keyval < 256)
1918         ConsoleAutoPopUp (buf);
1919 }
1920
1921 #ifdef TODO_GTK
1922 void
1923 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1924 {
1925         if (!TempBackwardActive) {
1926                 TempBackwardActive = True;
1927                 BackwardEvent();
1928         }
1929 }
1930
1931 void
1932 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1933 {
1934         /* Check to see if triggered by a key release event for a repeating key.
1935          * If so the next queued event will be a key press of the same key at the same time */
1936         if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1937                 XEvent next;
1938                 XPeekEvent(xDisplay, &next);
1939                 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1940                         next.xkey.keycode == event->xkey.keycode)
1941                                 return;
1942         }
1943     ForwardEvent();
1944         TempBackwardActive = False;
1945 }
1946 #endif
1947
1948 void
1949 ManProc ()
1950 {   // called from menu
1951 #ifdef __APPLE__
1952     char buf[MSG_SIZ];
1953     snprintf(buf, MSG_SIZ, "%s ./man.command", appData.sysOpen);
1954     system(buf);
1955 #else
1956     system("xterm -e man xboard &");
1957 #endif
1958 }
1959
1960 void
1961 SetWindowTitle (char *text, char *title, char *icon)
1962 {
1963 #ifdef TODO_GTK
1964     Arg args[16];
1965     int i;
1966     if (appData.titleInWindow) {
1967         i = 0;
1968         XtSetArg(args[i], XtNlabel, text);   i++;
1969         XtSetValues(titleWidget, args, i);
1970     }
1971     i = 0;
1972     XtSetArg(args[i], XtNiconName, (XtArgVal) icon);    i++;
1973     XtSetArg(args[i], XtNtitle, (XtArgVal) title);      i++;
1974     XtSetValues(shellWidget, args, i);
1975     XSync(xDisplay, False);
1976 #endif
1977     if (appData.titleInWindow) {
1978         SetWidgetLabel(titleWidget, text);
1979     }
1980     gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1981 }
1982
1983
1984 void
1985 DisplayIcsInteractionTitle (String message)
1986 {
1987 #ifdef TODO_GTK
1988   if (oldICSInteractionTitle == NULL) {
1989     /* Magic to find the old window title, adapted from vim */
1990     char *wina = getenv("WINDOWID");
1991     if (wina != NULL) {
1992       Window win = (Window) atoi(wina);
1993       Window root, parent, *children;
1994       unsigned int nchildren;
1995       int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
1996       for (;;) {
1997         if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
1998         if (!XQueryTree(xDisplay, win, &root, &parent,
1999                         &children, &nchildren)) break;
2000         if (children) XFree((void *)children);
2001         if (parent == root || parent == 0) break;
2002         win = parent;
2003       }
2004       XSetErrorHandler(oldHandler);
2005     }
2006     if (oldICSInteractionTitle == NULL) {
2007       oldICSInteractionTitle = "xterm";
2008     }
2009   }
2010   printf("\033]0;%s\007", message);
2011   fflush(stdout);
2012 #endif
2013 }
2014
2015
2016 void
2017 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
2018 {
2019     GtkWidget *w = (GtkWidget *) opt->handle;
2020     GdkColor col;
2021     char *markup;
2022     char bgcolor[10];
2023     char fgcolor[10];
2024
2025     if (highlight) {
2026         strcpy(bgcolor, "black");
2027         strcpy(fgcolor, "white");
2028     } else {
2029         strcpy(bgcolor, "white");
2030         strcpy(fgcolor, "black");
2031     }
2032     if (timer > 0 &&
2033         appData.lowTimeWarning &&
2034         (timer / 1000) < appData.icsAlarmTime) {
2035         strcpy(fgcolor, appData.lowTimeWarningColor);
2036     }
2037
2038     gdk_color_parse( bgcolor, &col );
2039     gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
2040
2041     if (appData.clockMode) {
2042         markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>", appData.clockFont,
2043                                          bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2044 //        markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
2045 //                                       bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2046     } else {
2047         markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s  </span>", appData.clockFont,
2048                                          bgcolor, fgcolor, color);
2049 //        markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s  </span>",
2050 //                                       bgcolor, fgcolor, color);
2051     }
2052     gtk_label_set_markup(GTK_LABEL(w), markup);
2053     g_free(markup);
2054 }
2055
2056 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
2057
2058 void
2059 SetClockIcon (int color)
2060 {
2061     GdkPixbuf *pm = *clockIcons[color];
2062     if (mainwindowIcon != pm) {
2063         mainwindowIcon = pm;
2064 #ifdef __APPLE__
2065         gtkosx_application_set_dock_icon_pixbuf(theApp, mainwindowIcon);
2066 #else
2067         gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
2068 #endif
2069     }
2070 }
2071
2072 #define INPUT_SOURCE_BUF_SIZE 8192
2073
2074 typedef struct {
2075     CPKind kind;
2076     int fd;
2077     int lineByLine;
2078     char *unused;
2079     InputCallback func;
2080     guint sid;
2081     char buf[INPUT_SOURCE_BUF_SIZE];
2082     VOIDSTAR closure;
2083 } InputSource;
2084
2085 gboolean
2086 DoInputCallback(io, cond, data)
2087      GIOChannel  *io;
2088      GIOCondition cond;
2089      gpointer    *data;
2090 {
2091   /* read input from one of the input source (for example a chess program, ICS, etc).
2092    * and call a function that will handle the input
2093    */
2094
2095     int count;
2096     int error;
2097     char *p, *q;
2098
2099     /* All information (callback function, file descriptor, etc) is
2100      * saved in an InputSource structure
2101      */
2102     InputSource *is = (InputSource *) data;
2103
2104     if (is->lineByLine) {
2105         count = read(is->fd, is->unused,
2106                      INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
2107         if (count <= 0) {
2108             if(count == 0 && is->kind == CPReal && shells[ChatDlg]) { // [HGM] absence of terminal is no error if ICS Console present
2109                 RemoveInputSource(is); // cease reading stdin
2110                 stdoutClosed = TRUE;   // suppress future output
2111                 return True;
2112             } 
2113             (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
2114             return True;
2115         }
2116         is->unused += count;
2117         p = is->buf;
2118         /* break input into lines and call the callback function on each
2119          * line
2120          */
2121         while (p < is->unused) {
2122             q = memchr(p, '\n', is->unused - p);
2123             if (q == NULL) break;
2124             q++;
2125             (is->func)(is, is->closure, p, q - p, 0);
2126             p = q;
2127         }
2128         /* remember not yet used part of the buffer */
2129         q = is->buf;
2130         while (p < is->unused) {
2131             *q++ = *p++;
2132         }
2133         is->unused = q;
2134     } else {
2135       /* read maximum length of input buffer and send the whole buffer
2136        * to the callback function
2137        */
2138         count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2139         if (count == -1)
2140           error = errno;
2141         else
2142           error = 0;
2143         (is->func)(is, is->closure, is->buf, count, error);
2144     }
2145     return True; // Must return true or the watch will be removed
2146 }
2147
2148 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2149      ProcRef pr;
2150      int lineByLine;
2151      InputCallback func;
2152      VOIDSTAR closure;
2153 {
2154     InputSource *is;
2155     GIOChannel *channel;
2156     ChildProc *cp = (ChildProc *) pr;
2157
2158     is = (InputSource *) calloc(1, sizeof(InputSource));
2159     is->lineByLine = lineByLine;
2160     is->func = func;
2161     if (pr == NoProc) {
2162         is->kind = CPReal;
2163         is->fd = fileno(stdin);
2164     } else {
2165         is->kind = cp->kind;
2166         is->fd = cp->fdFrom;
2167     }
2168     if (lineByLine)
2169       is->unused = is->buf;
2170     else
2171       is->unused = NULL;
2172
2173    /* GTK-TODO: will this work on windows?*/
2174
2175     channel = g_io_channel_unix_new(is->fd);
2176     g_io_channel_set_close_on_unref (channel, TRUE);
2177     is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2178
2179     is->closure = closure;
2180     return (InputSourceRef) is;
2181 }
2182
2183
2184 void
2185 RemoveInputSource(isr)
2186      InputSourceRef isr;
2187 {
2188     InputSource *is = (InputSource *) isr;
2189
2190     if (is->sid == 0) return;
2191     g_source_remove(is->sid);
2192     is->sid = 0;
2193     return;
2194 }
2195
2196 #ifndef HAVE_USLEEP
2197
2198 static Boolean frameWaiting;
2199
2200 static RETSIGTYPE
2201 FrameAlarm (int sig)
2202 {
2203   frameWaiting = False;
2204   /* In case System-V style signals.  Needed?? */
2205   signal(SIGALRM, FrameAlarm);
2206 }
2207
2208 void
2209 FrameDelay (int time)
2210 {
2211   struct itimerval delay;
2212
2213   if (time > 0) {
2214     frameWaiting = True;
2215     signal(SIGALRM, FrameAlarm);
2216     delay.it_interval.tv_sec =
2217       delay.it_value.tv_sec = time / 1000;
2218     delay.it_interval.tv_usec =
2219       delay.it_value.tv_usec = (time % 1000) * 1000;
2220     setitimer(ITIMER_REAL, &delay, NULL);
2221     while (frameWaiting) pause();
2222     delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2223     delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2224     setitimer(ITIMER_REAL, &delay, NULL);
2225   }
2226 }
2227
2228 #else
2229
2230 void
2231 FrameDelay (int time)
2232 {
2233 #ifdef TODO_GTK
2234   XSync(xDisplay, False);
2235 #endif
2236 //  gtk_main_iteration_do(False);
2237
2238   if (time > 0)
2239     usleep(time * 1000);
2240 }
2241
2242 #endif
2243
2244 static int
2245 FindLogo (char *place, char *name, char *buf)
2246 {   // check if file exists in given place
2247     FILE *f;
2248     if(!place) return 0;
2249     snprintf(buf, MSG_SIZ, "%s/%s.png", place, name);
2250     if(*place && strcmp(place, ".") && (f = fopen(buf, "r")) ) {
2251         fclose(f);
2252         return 1;
2253     }
2254     return 0;
2255 }
2256
2257 static void
2258 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2259 {
2260     char buf[MSG_SIZ], *logoName = buf;
2261     if(appData.logo[n][0]) {
2262         logoName = appData.logo[n];
2263     } else if(appData.autoLogo) {
2264         if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2265             sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2266         } else { // engine; cascade
2267             if(!FindLogo(appData.logoDir, cps->tidy, buf) &&   // first try user log folder
2268                !FindLogo(appData.directory[n], "logo", buf) && // then engine directory
2269                !FindLogo("/usr/local/share/games/plugins/logos", cps->tidy, buf) ) // then system folders
2270                 FindLogo("/usr/share/games/plugins/logos", cps->tidy, buf);
2271         }
2272     }
2273     if(logoName[0])
2274         { ASSIGN(cps->programLogo, logoName); }
2275 }
2276
2277 void
2278 UpdateLogos (int displ)
2279 {
2280     if(optList[W_WHITE-1].handle == NULL) return;
2281     LoadLogo(&first, 0, 0);
2282     LoadLogo(&second, 1, appData.icsActive);
2283     if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2284     return;
2285 }
2286
2287 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2288      char *label;
2289      char *def;
2290      char *filter;
2291      FileProc proc;
2292      char *openMode;
2293      Boolean pathFlag;
2294      char **name;
2295      FILE **fp;
2296 {
2297   GtkWidget     *dialog;
2298   GtkFileFilter *gtkfilter;
2299   GtkFileFilter *gtkfilter_all;
2300   char space[]     = " ";
2301   char fileext[10] = "";
2302   char *result     = NULL;
2303   char *cp;
2304
2305   /* make a copy of the filter string, so that strtok can work with it*/
2306   cp = strdup(filter);
2307
2308   /* add filters for file extensions */
2309   gtkfilter     = gtk_file_filter_new();
2310   gtkfilter_all = gtk_file_filter_new();
2311
2312   /* one filter to show everything */
2313   gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2314   gtk_file_filter_set_name   (gtkfilter_all, "All Files");
2315
2316   /* add filter if present */
2317   result = strtok(cp, space);
2318   while( result != NULL  ) {
2319     snprintf(fileext,10,"*%s",result);
2320     result = strtok( NULL, space );
2321     gtk_file_filter_add_pattern(gtkfilter, fileext);
2322   };
2323
2324   /* second filter to only show what's useful */
2325   gtk_file_filter_set_name (gtkfilter,filter);
2326
2327   if (openMode[0] == 'r')
2328     {
2329       dialog = gtk_file_chooser_dialog_new (label,
2330                                             NULL,
2331                                             GTK_FILE_CHOOSER_ACTION_OPEN,
2332                                             GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2333                                             GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2334                                             NULL);
2335     }
2336   else
2337     {
2338       dialog = gtk_file_chooser_dialog_new (label,
2339                                             NULL,
2340                                             GTK_FILE_CHOOSER_ACTION_SAVE,
2341                                             GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2342                                             GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2343                                             NULL);
2344       /* add filename suggestions */
2345       if (strlen(def) > 0 )
2346         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2347
2348       //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2349     }
2350
2351   /* add filters */
2352   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2353   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2354   /* activate filter */
2355   gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2356
2357   if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2358     {
2359       char *filename;
2360       FILE *f;
2361
2362       filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2363
2364       //see loadgamepopup
2365       f = fopen(filename, openMode);
2366       if (f == NULL)
2367         {
2368           DisplayError(_("Failed to open file"), errno);
2369         }
2370       else
2371         {
2372           /* TODO add indec */
2373             *fp = f;
2374             ASSIGN(*name, filename);
2375             ScheduleDelayedEvent(DelayedLoad, 50);
2376         }
2377       g_free (filename);
2378     };
2379
2380   gtk_widget_destroy (dialog);
2381   ModeHighlight();
2382
2383   free(cp);
2384   return;
2385
2386 }