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