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