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