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