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