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