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