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