Fix multi-leg promotions
[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     } else fontIsSet[fnr] = True;
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     if(!appData.fixedSize) gtk_widget_set_size_request(optList[W_BOARD].handle, 100, 100); // 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         static char dirName[MSG_SIZ];
988         getcwd(dirName, MSG_SIZ);
989         chessDir = dirName;
990     } else {
991         if (chdir(chessDir) != 0) {
992             fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
993             perror(chessDir);
994             exit(1);
995         }
996     }
997
998     if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
999         /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
1000         if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL)  {
1001            printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
1002            exit(errno);
1003         }
1004         setbuf(debugFP, NULL);
1005     }
1006
1007 #if ENABLE_NLS
1008     if (appData.debugMode) {
1009       fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
1010     }
1011 #endif
1012
1013     /* [HGM,HR] make sure board size is acceptable */
1014     if(appData.NrFiles > BOARD_FILES ||
1015        appData.NrRanks > BOARD_RANKS   )
1016          DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
1017
1018 #if !HIGHDRAG
1019     /* This feature does not work; animation needs a rewrite */
1020     appData.highlightDragging = FALSE;
1021 #endif
1022     InitBackEnd1();
1023
1024         gameInfo.variant = StringToVariant(appData.variant);
1025         InitPosition(FALSE);
1026
1027     /*
1028      * determine size, based on supplied or remembered -size, or screen size
1029      */
1030     if (isdigit(appData.boardSize[0])) {
1031         i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
1032                    &lineGap, &clockFontPxlSize, &coordFontPxlSize,
1033                    &fontPxlSize, &smallLayout, &tinyLayout);
1034         if (i == 0) {
1035             fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
1036                     programName, appData.boardSize);
1037             exit(2);
1038         }
1039         if (i < 7) {
1040             /* Find some defaults; use the nearest known size */
1041             SizeDefaults *szd, *nearest;
1042             int distance = 99999;
1043             nearest = szd = sizeDefaults;
1044             while (szd->name != NULL) {
1045                 if (abs(szd->squareSize - squareSize) < distance) {
1046                     nearest = szd;
1047                     distance = abs(szd->squareSize - squareSize);
1048                     if (distance == 0) break;
1049                 }
1050                 szd++;
1051             }
1052             if (i < 2) lineGap = nearest->lineGap;
1053             if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
1054             if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
1055             if (i < 5) fontPxlSize = nearest->fontPxlSize;
1056             if (i < 6) smallLayout = nearest->smallLayout;
1057             if (i < 7) tinyLayout = nearest->tinyLayout;
1058         }
1059     } else {
1060         SizeDefaults *szd = sizeDefaults;
1061         if (*appData.boardSize == NULLCHAR) {
1062 //            GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow)); // TODO: this does not work, as no mainwindow yet
1063             GdkScreen *screen = gdk_screen_get_default();
1064             guint screenwidth = gdk_screen_get_width(screen);
1065             guint screenheight = gdk_screen_get_height(screen);
1066             while (screenwidth  < (szd->minScreenSize*BOARD_WIDTH  + 4)/8 ||
1067                    screenheight < (szd->minScreenSize*BOARD_HEIGHT + 4)/8) {
1068               szd++;
1069             }
1070             if (szd->name == NULL) szd--;
1071             appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
1072         } else {
1073             while (szd->name != NULL &&
1074                    StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
1075             if (szd->name == NULL) {
1076                 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
1077                         programName, appData.boardSize);
1078                 exit(2);
1079             }
1080         }
1081         squareSize = szd->squareSize;
1082         lineGap = szd->lineGap;
1083         clockFontPxlSize = szd->clockFontPxlSize;
1084         coordFontPxlSize = szd->coordFontPxlSize;
1085         fontPxlSize = szd->fontPxlSize;
1086         smallLayout = szd->smallLayout;
1087         tinyLayout = szd->tinyLayout;
1088         // [HGM] font: use defaults from settings file if available and not overruled
1089     }
1090     initialSquareSize = squareSize; // [HGM] remember for saving font info
1091     if(BOARD_WIDTH != 8) {
1092         squareSize = (squareSize*8 + BOARD_WIDTH/2)/BOARD_WIDTH; // keep width the same
1093         lineGap = (squareSize < 37 ? 1 : squareSize < 59 ? 2 : squareSize < 116 ? 3 : 4);
1094     }
1095
1096     defaultLineGap = lineGap;
1097     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
1098
1099     /* [HR] height treated separately (hacked) */
1100     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
1101 //    boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1102
1103     /*
1104      * Determine what fonts to use.
1105      */
1106     InitializeFonts((2*clockFontPxlSize+1)/3, coordFontPxlSize, fontPxlSize);
1107
1108     /*
1109      * Detect if there are not enough colors available and adapt.
1110      */
1111 #ifdef TODO_GTK
1112     if (DefaultDepth(xDisplay, xScreen) <= 2) {
1113       appData.monoMode = True;
1114     }
1115 #endif
1116
1117     forceMono = MakeColors();
1118
1119     if (forceMono) {
1120       fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1121               programName);
1122         appData.monoMode = True;
1123     }
1124
1125     ParseIcsTextColors();
1126
1127     /*
1128      * widget hierarchy
1129      */
1130     if (tinyLayout) {
1131         layoutName = "tinyLayout";
1132     } else if (smallLayout) {
1133         layoutName = "smallLayout";
1134     } else {
1135         layoutName = "normalLayout";
1136     }
1137
1138     if(appData.logoSize) appData.logoSize = boardWidth/4-3;
1139     wpMain.width = -1; // prevent popup sizes window
1140     optList = BoardPopUp(squareSize, lineGap, (void*)
1141 #ifdef TODO_GTK
1142 #if ENABLE_NLS
1143                                                 &clockFontSet);
1144 #else
1145                                                 clockFontStruct);
1146 #endif
1147 #else
1148 0);
1149 #endif
1150     InitDrawingHandle(optList + W_BOARD);
1151     shellWidget      = shells[BoardWindow];
1152     currBoard        = &optList[W_BOARD];
1153     boardWidget      = optList[W_BOARD].handle;
1154     menuBarWidget    = optList[W_MENU].handle;
1155     dropMenu         = optList[W_DROP].handle;
1156     titleWidget = optList[optList[W_TITLE].type != Skip ? W_TITLE : W_SMALL].handle;
1157     DelayedDrag(); // fake configure event (i3wm tiling window manager fails to send one after initial resize)
1158 #ifdef TODO_GTK
1159     formWidget  = XtParent(boardWidget);
1160     XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1161     XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1162     XtGetValues(optList[W_WHITE].handle, args, 2);
1163     if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1164       XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1165       XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1166       XtGetValues(optList[W_PAUSE].handle, args, 2);
1167     }
1168 #endif
1169
1170     // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1171     //       not need to go into InitDrawingSizes().
1172
1173     InitMenuMarkers();
1174
1175     // add accelerators to main shell
1176     gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1177
1178     /*
1179      * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1180      */
1181     WhiteIcon  = LoadIconFile("icon_white");
1182     BlackIcon  = LoadIconFile("icon_black");
1183     SetClockIcon(0); // sets white icon
1184
1185
1186     /*
1187      * Create a cursor for the board widget.
1188      */
1189 #ifdef TODO_GTK
1190     window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1191     XChangeWindowAttributes(xDisplay, xBoardWindow,
1192                             CWCursor, &window_attributes);
1193 #endif
1194
1195     /*
1196      * Inhibit shell resizing.
1197      */
1198 #ifdef TODO_GTK
1199     shellArgs[0].value = (XtArgVal) &w;
1200     shellArgs[1].value = (XtArgVal) &h;
1201     XtGetValues(shellWidget, shellArgs, 2);
1202     shellArgs[4].value = shellArgs[2].value = w;
1203     shellArgs[5].value = shellArgs[3].value = h;
1204 //    XtSetValues(shellWidget, &shellArgs[2], 4);
1205 #endif
1206     {
1207         // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1208         // It wil only become known asynchronously, when we first write a string into it.
1209         // This will then change the clock widget height, which triggers resizing the top-level window
1210         // and a configure event. Only then can we know the total height of the top-level window,
1211         // and calculate the height we need. The clockKludge flag suppresses all resizing until
1212         // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1213         int hc;
1214         GtkAllocation a;
1215         gtk_widget_get_allocation(shells[BoardWindow], &a);
1216         w = a.width; h = a.height;
1217         gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1218         clockKludge = hc = a.height;
1219         gtk_widget_get_allocation(boardWidget, &a);
1220         marginW =  w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1221         marginH =  h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1222     }
1223
1224     CreateAnyPieces(1);
1225     CreateGrid();
1226
1227     if(appData.logoSize)
1228     {   // locate and read user logo
1229         char buf[MSG_SIZ], name[MSG_SIZ];
1230         snprintf(name, MSG_SIZ, "/home/%s", UserName());
1231         if(!FindLogo(name, ".logo", buf))
1232             FindLogo(appData.logoDir, name + 6, buf);
1233         ASSIGN(userLogo, buf);
1234     }
1235
1236     if (appData.animate || appData.animateDragging)
1237       CreateAnimVars();
1238
1239     g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1240     g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1241
1242     /* [AS] Restore layout */
1243     if( wpMoveHistory.visible ) {
1244       HistoryPopUp();
1245     }
1246
1247     if( wpEvalGraph.visible )
1248       {
1249         EvalGraphPopUp();
1250       };
1251
1252     if( wpEngineOutput.visible ) {
1253       EngineOutputPopUp();
1254     }
1255
1256     if( wpConsole.visible && appData.icsActive ) {
1257       ChatProc();
1258       BoardToTop();
1259     }
1260
1261     gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1262     InitPosition(TRUE);
1263
1264     InitBackEnd2();
1265
1266     if (errorExitStatus == -1) {
1267         if (appData.icsActive) {
1268             /* We now wait until we see "login:" from the ICS before
1269                sending the logon script (problems with timestamp otherwise) */
1270             /*ICSInitScript();*/
1271             if (appData.icsInputBox) ICSInputBoxPopUp();
1272         }
1273
1274     #ifdef SIGWINCH
1275     signal(SIGWINCH, TermSizeSigHandler);
1276     #endif
1277         signal(SIGINT, IntSigHandler);
1278         signal(SIGTERM, IntSigHandler);
1279         if (*appData.cmailGameName != NULLCHAR) {
1280             signal(SIGUSR1, CmailSigHandler);
1281         }
1282     }
1283
1284     UpdateLogos(TRUE);
1285 //    XtSetKeyboardFocus(shellWidget, formWidget);
1286 #ifdef TODO_GTK
1287     XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1288 #endif
1289
1290     /* check for GTK events and process them */
1291 //    gtk_main();
1292 while(1) {
1293 gtk_main_iteration();
1294 }
1295
1296     if (appData.debugMode) fclose(debugFP); // [DM] debug
1297     return 0;
1298 }
1299
1300 void
1301 DoEvents ()
1302 {
1303     while(gtk_events_pending()) gtk_main_iteration();
1304 }
1305
1306 RETSIGTYPE
1307 TermSizeSigHandler (int sig)
1308 {
1309     update_ics_width();
1310 }
1311
1312 RETSIGTYPE
1313 IntSigHandler (int sig)
1314 {
1315     ExitEvent(sig);
1316 }
1317
1318 RETSIGTYPE
1319 CmailSigHandler (int sig)
1320 {
1321     int dummy = 0;
1322     int error;
1323
1324     signal(SIGUSR1, SIG_IGN);   /* suspend handler     */
1325
1326     /* Activate call-back function CmailSigHandlerCallBack()             */
1327     OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1328
1329     signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1330 }
1331
1332 void
1333 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1334 {
1335     BoardToTop();
1336     ReloadCmailMsgEvent(TRUE);  /* Reload cmail msg  */
1337 }
1338 /**** end signal code ****/
1339
1340
1341 #define Abs(n) ((n)<0 ? -(n) : (n))
1342
1343 char *
1344 InsertPxlSize (char *pattern, int targetPxlSize)
1345 {
1346     char buf[MSG_SIZ];
1347     snprintf(buf, MSG_SIZ, pattern, targetPxlSize); // pattern is something like "Sans Bold %d"
1348     return strdup(buf);
1349 }
1350
1351 #ifdef ENABLE_NLS
1352 #ifdef TODO_GTK
1353 char *
1354 InsertPxlSize (char *pattern, int targetPxlSize)
1355 {
1356     char *base_fnt_lst, strInt[12], *p, *q;
1357     int alternatives, i, len, strIntLen;
1358
1359     /*
1360      * Replace the "*" (if present) in the pixel-size slot of each
1361      * alternative with the targetPxlSize.
1362      */
1363     p = pattern;
1364     alternatives = 1;
1365     while ((p = strchr(p, ',')) != NULL) {
1366       alternatives++;
1367       p++;
1368     }
1369     snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1370     strIntLen = strlen(strInt);
1371     base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1372
1373     p = pattern;
1374     q = base_fnt_lst;
1375     while (alternatives--) {
1376       char *comma = strchr(p, ',');
1377       for (i=0; i<14; i++) {
1378         char *hyphen = strchr(p, '-');
1379         if (!hyphen) break;
1380         if (comma && hyphen > comma) break;
1381         len = hyphen + 1 - p;
1382         if (i == 7 && *p == '*' && len == 2) {
1383           p += len;
1384           memcpy(q, strInt, strIntLen);
1385           q += strIntLen;
1386           *q++ = '-';
1387         } else {
1388           memcpy(q, p, len);
1389           p += len;
1390           q += len;
1391         }
1392       }
1393       if (!comma) break;
1394       len = comma + 1 - p;
1395       memcpy(q, p, len);
1396       p += len;
1397       q += len;
1398     }
1399     strcpy(q, p);
1400
1401     return base_fnt_lst;
1402 }
1403 #endif
1404
1405 #ifdef TODO_GTK
1406 XFontSet
1407 CreateFontSet (char *base_fnt_lst)
1408 {
1409     XFontSet fntSet;
1410     char **missing_list;
1411     int missing_count;
1412     char *def_string;
1413
1414     fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1415                             &missing_list, &missing_count, &def_string);
1416     if (appData.debugMode) {
1417       int i, count;
1418       XFontStruct **font_struct_list;
1419       char **font_name_list;
1420       fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1421       if (fntSet) {
1422         fprintf(debugFP, " got list %s, locale %s\n",
1423                 XBaseFontNameListOfFontSet(fntSet),
1424                 XLocaleOfFontSet(fntSet));
1425         count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1426         for (i = 0; i < count; i++) {
1427           fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1428         }
1429       }
1430       for (i = 0; i < missing_count; i++) {
1431         fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1432       }
1433     }
1434     if (fntSet == NULL) {
1435       fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1436       exit(2);
1437     }
1438     return fntSet;
1439 }
1440 #endif
1441 #else // not ENABLE_NLS
1442 /*
1443  * Find a font that matches "pattern" that is as close as
1444  * possible to the targetPxlSize.  Prefer fonts that are k
1445  * pixels smaller to fonts that are k pixels larger.  The
1446  * pattern must be in the X Consortium standard format,
1447  * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1448  * The return value should be freed with XtFree when no
1449  * longer needed.
1450  */
1451 #ifdef TODO_GTK
1452 char *
1453 FindFont (char *pattern, int targetPxlSize)
1454 {
1455     char **fonts, *p, *best, *scalable, *scalableTail;
1456     int i, j, nfonts, minerr, err, pxlSize;
1457
1458     fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1459     if (nfonts < 1) {
1460         fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1461                 programName, pattern);
1462         exit(2);
1463     }
1464
1465     best = fonts[0];
1466     scalable = NULL;
1467     minerr = 999999;
1468     for (i=0; i<nfonts; i++) {
1469         j = 0;
1470         p = fonts[i];
1471         if (*p != '-') continue;
1472         while (j < 7) {
1473             if (*p == NULLCHAR) break;
1474             if (*p++ == '-') j++;
1475         }
1476         if (j < 7) continue;
1477         pxlSize = atoi(p);
1478         if (pxlSize == 0) {
1479             scalable = fonts[i];
1480             scalableTail = p;
1481         } else {
1482             err = pxlSize - targetPxlSize;
1483             if (Abs(err) < Abs(minerr) ||
1484                 (minerr > 0 && err < 0 && -err == minerr)) {
1485                 best = fonts[i];
1486                 minerr = err;
1487             }
1488         }
1489     }
1490     if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1491         /* If the error is too big and there is a scalable font,
1492            use the scalable font. */
1493         int headlen = scalableTail - scalable;
1494         p = (char *) XtMalloc(strlen(scalable) + 10);
1495         while (isdigit(*scalableTail)) scalableTail++;
1496         sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1497     } else {
1498         p = (char *) XtMalloc(strlen(best) + 2);
1499         safeStrCpy(p, best, strlen(best)+1 );
1500     }
1501     if (appData.debugMode) {
1502         fprintf(debugFP, "resolved %s at pixel size %d\n  to %s\n",
1503                 pattern, targetPxlSize, p);
1504     }
1505     XFreeFontNames(fonts);
1506     return p;
1507 }
1508 #endif
1509 #endif
1510
1511 void
1512 MarkMenuItem (char *menuRef, int state)
1513 {
1514     MenuItem *item = MenuNameToItem(menuRef);
1515
1516     if(item && item->handle) {
1517         ((GtkCheckMenuItem *) (item->handle))->active = state;
1518     }
1519     SYNC_MENUBAR;
1520 }
1521
1522 void
1523 EnableNamedMenuItem (char *menuRef, int state)
1524 {
1525     MenuItem *item = MenuNameToItem(menuRef);
1526
1527     if(item && item->handle) gtk_widget_set_sensitive(item->handle, state);
1528     SYNC_MENUBAR;
1529 }
1530
1531 void
1532 EnableButtonBar (int state)
1533 {
1534 #ifdef TODO_GTK
1535     XtSetSensitive(optList[W_BUTTON].handle, state);
1536 #endif
1537 }
1538
1539
1540 void
1541 SetMenuEnables (Enables *enab)
1542 {
1543   while (enab->name != NULL) {
1544     EnableNamedMenuItem(enab->name, enab->value);
1545     enab++;
1546   }
1547 }
1548
1549 gboolean KeyPressProc(window, eventkey, data)
1550      GtkWindow *window;
1551      GdkEventKey  *eventkey;
1552      gpointer data;
1553 {
1554
1555     MoveTypeInProc(eventkey); // pop up for typed in moves
1556
1557 #ifdef TODO_GTK
1558     /* check for other key values */
1559     switch(eventkey->keyval) {
1560         case GDK_question:
1561           AboutGameEvent();
1562           break;
1563         default:
1564           break;
1565     }
1566 #endif
1567     return False;
1568 }
1569 #ifdef TODO_GTK
1570 void
1571 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1572 {   // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1573     MenuItem *item;
1574     if(*nprms == 0) return;
1575     item = MenuNameToItem(prms[0]);
1576     if(item) ((MenuProc *) item->proc) ();
1577 }
1578 #endif
1579
1580 void
1581 SetupDropMenu ()
1582 {
1583 #ifdef TODO_GTK
1584     int i, j, count;
1585     char label[32];
1586     Arg args[16];
1587     Widget entry;
1588     char* p;
1589
1590     for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1591         entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1592         p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1593                    dmEnables[i].piece);
1594         XtSetSensitive(entry, p != NULL || !appData.testLegality
1595                        /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1596                                        && !appData.icsActive));
1597         count = 0;
1598         while (p && *p++ == dmEnables[i].piece) count++;
1599         snprintf(label, sizeof(label), "%s  %d", dmEnables[i].widget, count);
1600         j = 0;
1601         XtSetArg(args[j], XtNlabel, label); j++;
1602         XtSetValues(entry, args, j);
1603     }
1604 #endif
1605 }
1606
1607 static void
1608 do_flash_delay (unsigned long msec)
1609 {
1610     TimeDelay(msec);
1611 }
1612
1613 void
1614 FlashDelay (int flash_delay)
1615 {
1616         if(flash_delay) do_flash_delay(flash_delay);
1617 }
1618
1619 double
1620 Fraction (int x, int start, int stop)
1621 {
1622    double f = ((double) x - start)/(stop - start);
1623    if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1624    return f;
1625 }
1626
1627 static WindowPlacement wpNew;
1628
1629 void
1630 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1631 {
1632     int touch=0, fudge = 4, f = 3;
1633     GetActualPlacement(sh, wp);
1634     if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x)         < fudge) touch = 1; else // right touch
1635     if(abs(wp->x + wp->width + 2*frameX - f - wpMain.x)            < fudge) touch = 2; else // left touch
1636     if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1637     if(abs(wp->y + wp->height + frameX + frameY - f - wpMain.y)    < fudge) touch = 4;      // top touch
1638 //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);
1639     if(!touch ) return; // only windows that touch co-move
1640     if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1641         int heightInc = wpNew.height - wpMain.height;
1642         double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1643         double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1644         wp->y += fracTop * heightInc;
1645         heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1646 #ifdef TODO_GTK
1647         if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1648 #endif
1649         wp->height += heightInc;
1650     } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1651         int widthInc = wpNew.width - wpMain.width;
1652         double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1653         double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1654         wp->y += fracLeft * widthInc;
1655         widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1656 #ifdef TODO_GTK
1657         if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1658 #endif
1659         wp->width += widthInc;
1660     }
1661     wp->x += wpNew.x - wpMain.x;
1662     wp->y += wpNew.y - wpMain.y;
1663     if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1664     if(touch == 3) wp->y += wpNew.height - wpMain.height;
1665 #ifdef TODO_GTK
1666     XtSetArg(args[j], XtNx, wp->x); j++;
1667     XtSetArg(args[j], XtNy, wp->y); j++;
1668     XtSetValues(sh, args, j);
1669 #endif
1670         gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1671 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1672         gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1673 }
1674
1675 void
1676 ReSize (WindowPlacement *wp)
1677 {
1678         GtkAllocation a;
1679         int sqx, sqy, i, w, h, lg = lineGap;
1680         if(wp->width == wpMain.width && wp->height == wpMain.height) 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+1].name && sizeDefaults[h].squareSize*8 > sqx*BOARD_WIDTH; h++) {}
1706         if(initialSquareSize != sizeDefaults[h].squareSize && !appData.fixedSize) { // 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.historyFont, 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             ApplyFont(&tagsOptions[1], NULL);
1719             ApplyFont(&commentOptions[0], NULL);
1720             ApplyFont(&historyOptions[0], NULL);
1721             ApplyFont(&engoutOptions[5], NULL);
1722             ApplyFont(&engoutOptions[12], NULL);
1723             ApplyFont(&chatOptions[11], appData.icsFont);
1724             AppendColorized(&chatOptions[6], NULL, 0); // kludge to replace font tag
1725         }
1726         if(!strchr(appData.boardSize, ',')) {
1727             ASSIGN(appData.boardSize, sizeDefaults[h].name);
1728         }
1729 #ifndef OSXAPP
1730         if(sizeDefaults[h].tinyLayout != tinyLayout) { // alter clipping of menu names to conform to board width
1731             int clip = (tinyLayout = sizeDefaults[h].tinyLayout) + 1;
1732             char text[MSG_SIZ];
1733             for(h=1; mainOptions[h].type == DropDown; h++) {
1734                 strncpy(text, _(mainOptions[h].name), MSG_SIZ);
1735                 if(clip != 1) text[clip + (text[clip-1] == '_')] = NULLCHAR;
1736                 gtk_menu_item_set_label((GtkMenuItem *) mainOptions[h].handle, text);
1737             }
1738         }
1739 #endif
1740         if(sqx != squareSize && !appData.fixedSize) {
1741             squareSize = sqx; // adopt new square size
1742             CreatePNGPieces(appData.pieceDirectory); // make newly scaled pieces
1743             InitDrawingSizes(0, 0); // creates grid etc.
1744         } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1745         w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1746         h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1747         if(optList[W_BOARD].max   > w) optList[W_BOARD].max = w;
1748         if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
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++) return; // prevent recursive calling, but remember we missed an event in 'busy'
1764
1765     if(delayedDragTag) g_source_remove(delayedDragTag); // no more timer interrupts from same event!
1766     delayedDragTag = 0;
1767
1768     do {
1769         GetActualPlacement(shellWidget, &wpNew);
1770         if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1771            wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1772             busy = 0; break; // false alarm
1773         }
1774         ReSize(&wpNew); // this can be interrupted by other events
1775         if(appData.useStickyWindows) {
1776             if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1777             if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1778             if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1779             if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1780             if(shellUp[ChatDlg]) CoDrag(shells[ChatDlg], &wpConsole);
1781         }
1782         wpMain = wpNew;
1783         DrawPosition(True, NULL);
1784         if(busy > 2) busy = 2; // if multiple events were backlogged, only do one more
1785     } while(--busy);
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 }