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