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