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