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