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