2 * xboard.c -- X front end for XBoard
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
10 * The following terms apply to Digital Equipment Corporation's copyright
12 * ------------------------------------------------------------------------
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.
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
30 * ------------------------------------------------------------------------
32 * The following terms apply to the enhanced version of XBoard
33 * distributed by the Free Software Foundation:
34 * ------------------------------------------------------------------------
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.
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.
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/. *
49 *------------------------------------------------------------------------
50 ** See the file ChangeLog for a revision history. */
60 #include <sys/types.h>
64 #include <cairo/cairo.h>
65 #include <cairo/cairo-xlib.h>
69 # if HAVE_SYS_SOCKET_H
70 # include <sys/socket.h>
71 # include <netinet/in.h>
73 # else /* not HAVE_SYS_SOCKET_H */
74 # if HAVE_LAN_SOCKET_H
75 # include <lan/socket.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 */
87 #else /* not STDC_HEADERS */
88 extern char *getenv();
91 # else /* not HAVE_STRING_H */
93 # endif /* not HAVE_STRING_H */
94 #endif /* not STDC_HEADERS */
97 # include <sys/fcntl.h>
98 #else /* not HAVE_SYS_FCNTL_H */
101 # endif /* HAVE_FCNTL_H */
102 #endif /* not HAVE_SYS_FCNTL_H */
104 #if HAVE_SYS_SYSTEMINFO_H
105 # include <sys/systeminfo.h>
106 #endif /* HAVE_SYS_SYSTEMINFO_H */
108 #if TIME_WITH_SYS_TIME
109 # include <sys/time.h>
113 # include <sys/time.h>
124 # include <sys/wait.h>
129 # define NAMLEN(dirent) strlen((dirent)->d_name)
130 # define HAVE_DIR_STRUCT
132 # define dirent direct
133 # define NAMLEN(dirent) (dirent)->d_namlen
135 # include <sys/ndir.h>
136 # define HAVE_DIR_STRUCT
139 # include <sys/dir.h>
140 # define HAVE_DIR_STRUCT
144 # define HAVE_DIR_STRUCT
152 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
155 #include "frontend.h"
157 #include "backendz.h"
165 #include "engineoutput.h"
171 # include "gtkmacintegration/gtkosxapplication.h"
172 // prevent pathname of positional file argument provided by OS X being be mistaken for option name
173 // (price is that we won't recognize Windows option format anymore).
175 // redefine some defaults
179 # define ICS_LOGON "Library/Preferences/XboardICS.conf"
180 # define SYSCONFDIR "../etc"
181 # define DATADIR dataDir
182 char *dataDir; // for expanding ~~
191 #define usleep(t) _sleep2(((t)+500)/1000)
195 # define _(s) gettext (s)
196 # define N_(s) gettext_noop (s)
202 int main P((int argc, char **argv));
203 RETSIGTYPE CmailSigHandler P((int sig));
204 RETSIGTYPE IntSigHandler P((int sig));
205 RETSIGTYPE TermSizeSigHandler P((int sig));
207 char *InsertPxlSize P((char *pattern, int targetPxlSize));
208 XFontSet CreateFontSet P((char *base_fnt_lst));
210 char *FindFont P((char *pattern, int targetPxlSize));
212 void DelayedDrag P((void));
213 void ICSInputBoxPopUp P((void));
214 void MoveTypeInProc P((GdkEventKey *eventkey));
215 gboolean KeyPressProc P((GtkWindow *window, GdkEventKey *eventkey, gpointer data));
216 Boolean TempBackwardActive = False;
217 void DisplayMove P((int moveNumber));
218 void update_ics_width P(());
219 int CopyMemoProc P(());
220 static gboolean EventProc P((GtkWidget *widget, GdkEvent *event, gpointer g));
224 XFontSet fontSet, clockFontSet;
227 XFontStruct *clockFontStruct;
229 Font coordFontID, countFontID;
230 XFontStruct *coordFontStruct, *countFontStruct;
232 void *shellWidget, *formWidget, *boardWidget, *titleWidget, *dropMenu, *menuBarWidget;
233 GtkWidget *mainwindow;
235 Option *optList; // contains all widgets of main window
238 char installDir[] = "."; // [HGM] UCI: needed for UCI; probably needs run-time initializtion
241 static GdkPixbuf *mainwindowIcon=NULL;
242 static GdkPixbuf *WhiteIcon=NULL;
243 static GdkPixbuf *BlackIcon=NULL;
245 /* key board accelerators */
246 GtkAccelGroup *GtkAccelerators;
248 typedef unsigned int BoardSize;
250 Boolean chessProgram;
252 int minX, minY; // [HGM] placement: volatile limits on upper-left corner
253 int smallLayout = 0, tinyLayout = 0,
254 marginW, marginH, // [HGM] for run-time resizing
255 fromX = -1, fromY = -1, toX, toY, commentUp = False,
256 errorExitStatus = -1, defaultLineGap;
258 Dimension textHeight;
260 char *chessDir, *programName, *programVersion;
261 Boolean alwaysOnTop = False;
262 char *icsTextMenuString;
264 char *firstChessProgramNames;
265 char *secondChessProgramNames;
267 WindowPlacement wpMain;
268 WindowPlacement wpConsole;
269 WindowPlacement wpComment;
270 WindowPlacement wpMoveHistory;
271 WindowPlacement wpEvalGraph;
272 WindowPlacement wpEngineOutput;
273 WindowPlacement wpGameList;
274 WindowPlacement wpTags;
275 WindowPlacement wpDualBoard;
277 /* This magic number is the number of intermediate frames used
278 in each half of the animation. For short moves it's reduced
279 by 1. The total number of frames will be factor * 2 + 1. */
282 SizeDefaults sizeDefaults[] = SIZE_DEFAULTS;
289 DropMenuEnables dmEnables[] = {
298 XtResource clientResources[] = {
299 { "flashCount", "flashCount", XtRInt, sizeof(int),
300 XtOffset(AppDataPtr, flashCount), XtRImmediate,
301 (XtPointer) FLASH_COUNT },
305 /* keyboard shortcuts not yet transistioned int menuitem @ menu.c */
306 char globalTranslations[] =
307 ":Ctrl<Key>Down: LoadSelectedProc(3) \n \
308 :Ctrl<Key>Up: LoadSelectedProc(-3) \n \
309 :<KeyDown>Return: TempBackwardProc() \n \
310 :<KeyUp>Return: TempForwardProc() \n";
312 char ICSInputTranslations[] =
313 "<Key>Up: UpKeyProc() \n "
314 "<Key>Down: DownKeyProc() \n "
315 "<Key>Return: EnterKeyProc() \n";
317 // [HGM] vari: another hideous kludge: call extend-end first so we can be sure select-start works,
318 // as the widget is destroyed before the up-click can call extend-end
319 char commentTranslations[] = "<Btn3Down>: extend-end() select-start() CommentClick() \n";
322 String xboardResources[] = {
323 "*Error*translations: #override\\n <Key>Return: ErrorPopDown()",
331 gtk_window_present(GTK_WINDOW(shells[BoardWindow]));
334 //---------------------------------------------------------------------------------------------------------
335 // some symbol definitions to provide the proper (= XBoard) context for the code in args.h
338 #define CW_USEDEFAULT (1<<31)
339 #define ICS_TEXT_MENU_SIZE 90
340 #define DEBUG_FILE "xboard.debug"
341 #define SetCurrentDirectory chdir
342 #define GetCurrentDirectory(SIZE, NAME) getcwd(NAME, SIZE)
346 // The option definition and parsing code common to XBoard and WinBoard is collected in this file
349 // front-end part of option handling
351 // [HGM] This platform-dependent table provides the location for storing the color info
352 extern char *crWhite, * crBlack;
356 &appData.whitePieceColor,
357 &appData.blackPieceColor,
358 &appData.lightSquareColor,
359 &appData.darkSquareColor,
360 &appData.highlightSquareColor,
361 &appData.premoveHighlightColor,
362 &appData.lowTimeWarningColor,
373 // [HGM] font: keep a font for each square size, even non-stndard ones
376 Boolean fontIsSet[NUM_FONTS], fontValid[NUM_FONTS][MAX_SIZE];
377 char *fontTable[NUM_FONTS][MAX_SIZE];
380 ParseFont (char *name, int number)
381 { // in XBoard, only 2 of the fonts are currently implemented, and we just copy their name
383 if(sscanf(name, "size%d:", &size)) {
384 // [HGM] font: font is meant for specific boardSize (likely from settings file);
385 // defer processing it until we know if it matches our board size
386 if(size >= 0 && size<MAX_SIZE) { // for now, fixed limit
387 fontTable[number][size] = strdup(strchr(name, ':')+1);
388 fontValid[number][size] = True;
393 case 0: // CLOCK_FONT
394 appData.clockFont = strdup(name);
396 case 1: // MESSAGE_FONT
397 appData.font = strdup(name);
399 case 2: // COORD_FONT
400 appData.coordFont = strdup(name);
405 fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
410 { // only 2 fonts currently
411 appData.clockFont = CLOCK_FONT_NAME;
412 appData.coordFont = COORD_FONT_NAME;
413 appData.font = DEFAULT_FONT_NAME;
418 { // no-op, until we identify the code for this already in XBoard and move it here
422 ParseColor (int n, char *name)
423 { // in XBoard, just copy the color-name string
424 if(colorVariable[n]) *(char**)colorVariable[n] = strdup(name);
428 ParseTextAttribs (ColorClass cc, char *s)
430 (&appData.colorShout)[cc] = strdup(s);
434 ParseBoardSize (void *addr, char *name)
436 appData.boardSize = strdup(name);
441 { // In XBoard the sound-playing program takes care of obtaining the actual sound
445 SetCommPortDefaults ()
446 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
449 // [HGM] args: these three cases taken out to stay in front-end
451 SaveFontArg (FILE *f, ArgDescriptor *ad)
454 int i, n = (int)(intptr_t)ad->argLoc;
456 case 0: // CLOCK_FONT
457 name = appData.clockFont;
459 case 1: // MESSAGE_FONT
462 case 2: // COORD_FONT
463 name = appData.coordFont;
468 for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
469 if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
470 fontTable[n][squareSize] = strdup(name);
471 fontValid[n][squareSize] = True;
474 for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
475 fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
480 { // nothing to do, as the sounds are at all times represented by their text-string names already
484 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
485 { // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
486 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
490 SaveColor (FILE *f, ArgDescriptor *ad)
491 { // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
492 if(colorVariable[(int)(intptr_t)ad->argLoc])
493 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
497 SaveBoardSize (FILE *f, char *name, void *addr)
498 { // wrapper to shield back-end from BoardSize & sizeInfo
499 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
503 ParseCommPortSettings (char *s)
504 { // no such option in XBoard (yet)
510 GetActualPlacement (GtkWidget *shell, WindowPlacement *wp)
514 gtk_widget_get_allocation(shell, &a);
515 gtk_window_get_position(GTK_WINDOW(shell), &a.x, &a.y);
519 wp->height = a.height;
520 //printf("placement: (%d,%d) %dx%d\n", a.x, a.y, a.width, a.height);
521 frameX = 3; frameY = 3; // remember to decide if windows touch
526 { // wrapper to shield use of window handles from back-end (make addressible by number?)
527 // In XBoard this will have to wait until awareness of window parameters is implemented
528 GetActualPlacement(shellWidget, &wpMain);
529 if(shellUp[EngOutDlg]) GetActualPlacement(shells[EngOutDlg], &wpEngineOutput);
530 if(shellUp[HistoryDlg]) GetActualPlacement(shells[HistoryDlg], &wpMoveHistory);
531 if(shellUp[EvalGraphDlg]) GetActualPlacement(shells[EvalGraphDlg], &wpEvalGraph);
532 if(shellUp[GameListDlg]) GetActualPlacement(shells[GameListDlg], &wpGameList);
533 if(shellUp[CommentDlg]) GetActualPlacement(shells[CommentDlg], &wpComment);
534 if(shellUp[TagsDlg]) GetActualPlacement(shells[TagsDlg], &wpTags);
538 PrintCommPortSettings (FILE *f, char *name)
539 { // This option does not exist in XBoard
543 EnsureOnScreen (int *x, int *y, int minX, int minY)
550 { // [HGM] args: allows testing if main window is realized from back-end
551 return DialogExists(BoardWindow);
555 PopUpStartupDialog ()
556 { // start menu not implemented in XBoard
560 ConvertToLine (int argc, char **argv)
562 static char line[128*1024], buf[1024];
566 for(i=1; i<argc; i++)
568 if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
569 && argv[i][0] != '{' )
570 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
572 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
573 strncat(line, buf, 128*1024 - strlen(line) - 1 );
576 line[strlen(line)-1] = NULLCHAR;
580 //--------------------------------------------------------------------------------------------
585 ResizeBoardWindow (int w, int h, int inhibit)
588 if(clockKludge) return; // ignore as long as clock does not have final height
589 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
590 w += marginW + 1; // [HGM] not sure why the +1 is (sometimes) needed...
591 h += marginH + a.height + 1;
592 gtk_window_resize(GTK_WINDOW(shellWidget), w, h);
597 { // dummy, as the GTK code does not make colors in advance
602 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
603 { // determine what fonts to use, and create them
608 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
609 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
610 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
611 appData.font = fontTable[MESSAGE_FONT][squareSize];
612 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
613 appData.coordFont = fontTable[COORD_FONT][squareSize];
616 appData.font = InsertPxlSize(appData.font, fontPxlSize);
617 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
618 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
619 fontSet = CreateFontSet(appData.font);
620 clockFontSet = CreateFontSet(appData.clockFont);
622 /* For the coordFont, use the 0th font of the fontset. */
623 XFontSet coordFontSet = CreateFontSet(appData.coordFont);
624 XFontStruct **font_struct_list;
625 XFontSetExtents *fontSize;
626 char **font_name_list;
627 XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
628 coordFontID = XLoadFont(xDisplay, font_name_list[0]);
629 coordFontStruct = XQueryFont(xDisplay, coordFontID);
630 fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
631 textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
634 appData.font = FindFont(appData.font, fontPxlSize);
635 appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
636 appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
637 clockFontID = XLoadFont(xDisplay, appData.clockFont);
638 clockFontStruct = XQueryFont(xDisplay, clockFontID);
639 coordFontID = XLoadFont(xDisplay, appData.coordFont);
640 coordFontStruct = XQueryFont(xDisplay, coordFontID);
641 // textHeight in !NLS mode!
643 countFontID = coordFontID; // [HGM] holdings
644 countFontStruct = coordFontStruct;
646 xdb = XtDatabase(xDisplay);
648 XrmPutLineResource(&xdb, "*international: True");
649 vTo.size = sizeof(XFontSet);
650 vTo.addr = (XtPointer) &fontSet;
651 XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
653 XrmPutStringResource(&xdb, "*font", appData.font);
664 case ArgInt: p = " N"; break;
665 case ArgString: p = " STR"; break;
666 case ArgBoolean: p = " TF"; break;
667 case ArgSettingsFilename:
668 case ArgBackupSettingsFile:
669 case ArgFilename: p = " FILE"; break;
670 case ArgX: p = " Nx"; break;
671 case ArgY: p = " Ny"; break;
672 case ArgAttribs: p = " TEXTCOL"; break;
673 case ArgColor: p = " COL"; break;
674 case ArgFont: p = " FONT"; break;
675 case ArgBoardSize: p = " SIZE"; break;
676 case ArgFloat: p = " FLOAT"; break;
681 case ArgCommSettings:
693 ArgDescriptor *q, *p = argDescriptors+5;
694 printf("\nXBoard accepts the following options:\n"
695 "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
696 " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
697 " SIZE = board-size spec(s)\n"
698 " Within parentheses are short forms, or options to set to true or false.\n"
699 " Persistent options (saved in the settings file) are marked with *)\n\n");
701 if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
702 snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
703 if(p->save) strcat(buf+len, "*");
704 for(q=p+1; q->argLoc == p->argLoc; q++) {
705 if(q->argName[0] == '-') continue;
706 strcat(buf+len, q == p+1 ? " (" : " ");
707 sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
709 if(q != p+1) strcat(buf+len, ")");
711 if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
714 if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
718 SlaveResize (Option *opt)
720 static int slaveW, slaveH, w, h;
723 gtk_widget_get_allocation(shells[DummyDlg], &a);
724 w = a.width; h = a.height;
725 gtk_widget_get_allocation(opt->handle, &a);
726 slaveW = w - opt->max; // [HGM] needed to set new shellWidget size when we resize board
727 slaveH = h - a.height + 13;
729 gtk_window_resize(GTK_WINDOW(shells[DummyDlg]), slaveW + opt->max, slaveH + opt->value);
733 static char clickedFile[MSG_SIZ];
737 StartNewXBoard(GtkosxApplication *app, gchar *path, gpointer user_data)
738 { // handler of OSX OpenFile signal, which sends us the filename of clicked file or first argument
739 if(suppress) { // we just started XBoard without arguments
740 strncpy(clickedFile, path, MSG_SIZ); // remember file name, but otherwise ignore
741 } else { // we are running something presumably useful
743 snprintf(buf, MSG_SIZ, "open -n -a \"xboard\" --args \"%s\"", path);
744 system(buf); // start new instance on this file
751 main (int argc, char **argv)
753 int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
754 int boardWidth, boardHeight, w, h;
756 int forceMono = False;
758 srandom(time(0)); // [HGM] book: make random truly random
760 setbuf(stdout, NULL);
761 setbuf(stderr, NULL);
764 if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
765 printf("%s version %s\n\n configure options: %s\n", PACKAGE_NAME, PACKAGE_VERSION, CONFIGURE_OPTIONS);
769 if(argc > 1 && !strcmp(argv[1], "--help" )) {
775 gtk_init (&argc, &argv);
777 { // prepare to catch OX OpenFile signal, which will tell us the clicked file
778 GtkosxApplication *theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
779 dataDir = gtkosx_application_get_bundle_path();
780 g_signal_connect(theApp, "NSApplicationOpenFile", G_CALLBACK(StartNewXBoard), NULL);
781 // we must call application ready before we can get the signal,
782 // and supply a (dummy) menu bar before that, to avoid problems with dual apples in it
783 gtkosx_application_set_menu_bar(theApp, GTK_MENU_SHELL(gtk_menu_bar_new()));
784 gtkosx_application_ready(theApp);
785 suppress = (argc == 1 || argc > 1 && argv[1][00] != '-'); // OSX sends signal even if name was already argv[1]!
786 if(argc == 1) { // called without args: OSX open-file signal might follow
787 static char *fakeArgv[3] = {NULL, clickedFile, NULL};
788 usleep(10000); // wait 10 msec (and hope this is long enough).
789 while(gtk_events_pending())
790 gtk_main_iteration(); // process all events that came in upto now
791 suppress = 0; // future open-file signals should start new instance
792 if(clickedFile[0]) { // we were sent an open-file signal with filename!
793 fakeArgv[0] = argv[0];
794 argc = 2; argv = fakeArgv; // fake that we were called as "xboard filename"
800 /* set up keyboard accelerators group */
801 GtkAccelerators = gtk_accel_group_new();
803 programName = strrchr(argv[0], '/');
804 if (programName == NULL)
805 programName = argv[0];
810 // if (appData.debugMode) {
811 // fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
814 bindtextdomain(PACKAGE, LOCALEDIR);
815 bind_textdomain_codeset(PACKAGE, "UTF-8"); // needed when creating markup for the clocks
819 appData.boardSize = "";
820 InitAppData(ConvertToLine(argc, argv));
822 if (p == NULL) p = "/tmp";
823 i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
824 gameCopyFilename = (char*) malloc(i);
825 gamePasteFilename = (char*) malloc(i);
826 snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
827 snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
829 { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
830 static char buf[MSG_SIZ];
831 EscapeExpand(buf, appData.firstInitString);
832 appData.firstInitString = strdup(buf);
833 EscapeExpand(buf, appData.secondInitString);
834 appData.secondInitString = strdup(buf);
835 EscapeExpand(buf, appData.firstComputerString);
836 appData.firstComputerString = strdup(buf);
837 EscapeExpand(buf, appData.secondComputerString);
838 appData.secondComputerString = strdup(buf);
841 if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
844 if (chdir(chessDir) != 0) {
845 fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
851 if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
852 /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
853 if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL) {
854 printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
857 setbuf(debugFP, NULL);
861 if (appData.debugMode) {
862 fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
866 /* [HGM,HR] make sure board size is acceptable */
867 if(appData.NrFiles > BOARD_FILES ||
868 appData.NrRanks > BOARD_RANKS )
869 DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
872 /* This feature does not work; animation needs a rewrite */
873 appData.highlightDragging = FALSE;
877 gameInfo.variant = StringToVariant(appData.variant);
881 * determine size, based on supplied or remembered -size, or screen size
883 if (isdigit(appData.boardSize[0])) {
884 i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
885 &lineGap, &clockFontPxlSize, &coordFontPxlSize,
886 &fontPxlSize, &smallLayout, &tinyLayout);
888 fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
889 programName, appData.boardSize);
893 /* Find some defaults; use the nearest known size */
894 SizeDefaults *szd, *nearest;
895 int distance = 99999;
896 nearest = szd = sizeDefaults;
897 while (szd->name != NULL) {
898 if (abs(szd->squareSize - squareSize) < distance) {
900 distance = abs(szd->squareSize - squareSize);
901 if (distance == 0) break;
905 if (i < 2) lineGap = nearest->lineGap;
906 if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
907 if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
908 if (i < 5) fontPxlSize = nearest->fontPxlSize;
909 if (i < 6) smallLayout = nearest->smallLayout;
910 if (i < 7) tinyLayout = nearest->tinyLayout;
913 SizeDefaults *szd = sizeDefaults;
914 if (*appData.boardSize == NULLCHAR) {
915 GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow));
916 guint screenwidth = gdk_screen_get_width(screen);
917 guint screenheight = gdk_screen_get_height(screen);
918 while (screenwidth < szd->minScreenSize ||
919 screenheight < szd->minScreenSize) {
922 if (szd->name == NULL) szd--;
923 appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
925 while (szd->name != NULL &&
926 StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
927 if (szd->name == NULL) {
928 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
929 programName, appData.boardSize);
933 squareSize = szd->squareSize;
934 lineGap = szd->lineGap;
935 clockFontPxlSize = szd->clockFontPxlSize;
936 coordFontPxlSize = szd->coordFontPxlSize;
937 fontPxlSize = szd->fontPxlSize;
938 smallLayout = szd->smallLayout;
939 tinyLayout = szd->tinyLayout;
940 // [HGM] font: use defaults from settings file if available and not overruled
943 defaultLineGap = lineGap;
944 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
946 /* [HR] height treated separately (hacked) */
947 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
948 boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
951 * Determine what fonts to use.
954 InitializeFonts(clockFontPxlSize, coordFontPxlSize, fontPxlSize);
958 * Detect if there are not enough colors available and adapt.
961 if (DefaultDepth(xDisplay, xScreen) <= 2) {
962 appData.monoMode = True;
966 forceMono = MakeColors();
969 fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
971 appData.monoMode = True;
974 ParseIcsTextColors();
980 layoutName = "tinyLayout";
981 } else if (smallLayout) {
982 layoutName = "smallLayout";
984 layoutName = "normalLayout";
987 wpMain.width = -1; // prevent popup sizes window
988 optList = BoardPopUp(squareSize, lineGap, (void*)
998 InitDrawingHandle(optList + W_BOARD);
999 shellWidget = shells[BoardWindow];
1000 currBoard = &optList[W_BOARD];
1001 boardWidget = optList[W_BOARD].handle;
1002 menuBarWidget = optList[W_MENU].handle;
1003 dropMenu = optList[W_DROP].handle;
1004 titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1006 formWidget = XtParent(boardWidget);
1007 XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1008 XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1009 XtGetValues(optList[W_WHITE].handle, args, 2);
1010 if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1011 XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1012 XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1013 XtGetValues(optList[W_PAUSE].handle, args, 2);
1017 // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1018 // not need to go into InitDrawingSizes().
1022 // add accelerators to main shell
1023 gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1026 * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1028 WhiteIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_white.svg", NULL);
1029 BlackIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_black.svg", NULL);
1030 mainwindowIcon = WhiteIcon;
1031 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1035 * Create a cursor for the board widget.
1038 window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1039 XChangeWindowAttributes(xDisplay, xBoardWindow,
1040 CWCursor, &window_attributes);
1044 * Inhibit shell resizing.
1047 shellArgs[0].value = (XtArgVal) &w;
1048 shellArgs[1].value = (XtArgVal) &h;
1049 XtGetValues(shellWidget, shellArgs, 2);
1050 shellArgs[4].value = shellArgs[2].value = w;
1051 shellArgs[5].value = shellArgs[3].value = h;
1052 // XtSetValues(shellWidget, &shellArgs[2], 4);
1055 // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1056 // It wil only become known asynchronously, when we first write a string into it.
1057 // This will then change the clock widget height, which triggers resizing the top-level window
1058 // and a configure event. Only then can we know the total height of the top-level window,
1059 // and calculate the height we need. The clockKludge flag suppresses all resizing until
1060 // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1063 gtk_widget_get_allocation(shells[BoardWindow], &a);
1064 w = a.width; h = a.height;
1065 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1066 clockKludge = hc = a.height;
1067 gtk_widget_get_allocation(boardWidget, &a);
1068 marginW = w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1069 marginH = h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1075 if(appData.logoSize)
1076 { // locate and read user logo
1078 snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1079 ASSIGN(userLogo, buf);
1082 if (appData.animate || appData.animateDragging)
1085 g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1086 g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1088 /* [AS] Restore layout */
1089 if( wpMoveHistory.visible ) {
1093 if( wpEvalGraph.visible )
1098 if( wpEngineOutput.visible ) {
1099 EngineOutputPopUp();
1104 if (errorExitStatus == -1) {
1105 if (appData.icsActive) {
1106 /* We now wait until we see "login:" from the ICS before
1107 sending the logon script (problems with timestamp otherwise) */
1108 /*ICSInitScript();*/
1109 if (appData.icsInputBox) ICSInputBoxPopUp();
1113 signal(SIGWINCH, TermSizeSigHandler);
1115 signal(SIGINT, IntSigHandler);
1116 signal(SIGTERM, IntSigHandler);
1117 if (*appData.cmailGameName != NULLCHAR) {
1118 signal(SIGUSR1, CmailSigHandler);
1122 gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1125 // XtSetKeyboardFocus(shellWidget, formWidget);
1127 XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1130 /* check for GTK events and process them */
1133 gtk_main_iteration();
1136 if (appData.debugMode) fclose(debugFP); // [DM] debug
1141 TermSizeSigHandler (int sig)
1147 IntSigHandler (int sig)
1153 CmailSigHandler (int sig)
1158 signal(SIGUSR1, SIG_IGN); /* suspend handler */
1160 /* Activate call-back function CmailSigHandlerCallBack() */
1161 OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1163 signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1167 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1170 ReloadCmailMsgEvent(TRUE); /* Reload cmail msg */
1172 /**** end signal code ****/
1175 #define Abs(n) ((n)<0 ? -(n) : (n))
1179 InsertPxlSize (char *pattern, int targetPxlSize)
1181 char *base_fnt_lst, strInt[12], *p, *q;
1182 int alternatives, i, len, strIntLen;
1185 * Replace the "*" (if present) in the pixel-size slot of each
1186 * alternative with the targetPxlSize.
1190 while ((p = strchr(p, ',')) != NULL) {
1194 snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1195 strIntLen = strlen(strInt);
1196 base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1200 while (alternatives--) {
1201 char *comma = strchr(p, ',');
1202 for (i=0; i<14; i++) {
1203 char *hyphen = strchr(p, '-');
1205 if (comma && hyphen > comma) break;
1206 len = hyphen + 1 - p;
1207 if (i == 7 && *p == '*' && len == 2) {
1209 memcpy(q, strInt, strIntLen);
1219 len = comma + 1 - p;
1226 return base_fnt_lst;
1231 CreateFontSet (char *base_fnt_lst)
1234 char **missing_list;
1238 fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1239 &missing_list, &missing_count, &def_string);
1240 if (appData.debugMode) {
1242 XFontStruct **font_struct_list;
1243 char **font_name_list;
1244 fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1246 fprintf(debugFP, " got list %s, locale %s\n",
1247 XBaseFontNameListOfFontSet(fntSet),
1248 XLocaleOfFontSet(fntSet));
1249 count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1250 for (i = 0; i < count; i++) {
1251 fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1254 for (i = 0; i < missing_count; i++) {
1255 fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1258 if (fntSet == NULL) {
1259 fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1265 #else // not ENABLE_NLS
1267 * Find a font that matches "pattern" that is as close as
1268 * possible to the targetPxlSize. Prefer fonts that are k
1269 * pixels smaller to fonts that are k pixels larger. The
1270 * pattern must be in the X Consortium standard format,
1271 * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1272 * The return value should be freed with XtFree when no
1276 FindFont (char *pattern, int targetPxlSize)
1278 char **fonts, *p, *best, *scalable, *scalableTail;
1279 int i, j, nfonts, minerr, err, pxlSize;
1282 fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1284 fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1285 programName, pattern);
1292 for (i=0; i<nfonts; i++) {
1295 if (*p != '-') continue;
1297 if (*p == NULLCHAR) break;
1298 if (*p++ == '-') j++;
1300 if (j < 7) continue;
1303 scalable = fonts[i];
1306 err = pxlSize - targetPxlSize;
1307 if (Abs(err) < Abs(minerr) ||
1308 (minerr > 0 && err < 0 && -err == minerr)) {
1314 if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1315 /* If the error is too big and there is a scalable font,
1316 use the scalable font. */
1317 int headlen = scalableTail - scalable;
1318 p = (char *) XtMalloc(strlen(scalable) + 10);
1319 while (isdigit(*scalableTail)) scalableTail++;
1320 sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1322 p = (char *) XtMalloc(strlen(best) + 2);
1323 safeStrCpy(p, best, strlen(best)+1 );
1325 if (appData.debugMode) {
1326 fprintf(debugFP, "resolved %s at pixel size %d\n to %s\n",
1327 pattern, targetPxlSize, p);
1329 XFreeFontNames(fonts);
1336 EnableNamedMenuItem (char *menuRef, int state)
1338 MenuItem *item = MenuNameToItem(menuRef);
1340 if(item) gtk_widget_set_sensitive(item->handle, state);
1344 EnableButtonBar (int state)
1347 XtSetSensitive(optList[W_BUTTON].handle, state);
1353 SetMenuEnables (Enables *enab)
1355 while (enab->name != NULL) {
1356 EnableNamedMenuItem(enab->name, enab->value);
1361 gboolean KeyPressProc(window, eventkey, data)
1363 GdkEventKey *eventkey;
1367 MoveTypeInProc(eventkey); // pop up for typed in moves
1370 /* check for other key values */
1371 switch(eventkey->keyval) {
1383 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1384 { // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1386 if(*nprms == 0) return;
1387 item = MenuNameToItem(prms[0]);
1388 if(item) ((MenuProc *) item->proc) ();
1402 for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1403 entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1404 p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1405 dmEnables[i].piece);
1406 XtSetSensitive(entry, p != NULL || !appData.testLegality
1407 /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1408 && !appData.icsActive));
1410 while (p && *p++ == dmEnables[i].piece) count++;
1411 snprintf(label, sizeof(label), "%s %d", dmEnables[i].widget, count);
1413 XtSetArg(args[j], XtNlabel, label); j++;
1414 XtSetValues(entry, args, j);
1420 do_flash_delay (unsigned long msec)
1426 FlashDelay (int flash_delay)
1428 if(flash_delay) do_flash_delay(flash_delay);
1432 Fraction (int x, int start, int stop)
1434 double f = ((double) x - start)/(stop - start);
1435 if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1439 static WindowPlacement wpNew;
1442 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1444 int touch=0, fudge = 2, f = 2;
1445 GetActualPlacement(sh, wp);
1446 if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x) < fudge) touch = 1; else // right touch
1447 if(abs(wp->x + wp->width + 2*frameX + f - wpMain.x) < fudge) touch = 2; else // left touch
1448 if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1449 if(abs(wp->y + wp->height + frameX + frameY + f - wpMain.y) < fudge) touch = 4; // top touch
1450 //printf("CoDrag: touch = %d x=%d w=%d x2=%d w2=%d fx=%d\n", touch, wpMain.x, wpMain.width, wp->x, wp->width, frameX);
1451 if(!touch ) return; // only windows that touch co-move
1452 if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1453 int heightInc = wpNew.height - wpMain.height;
1454 double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1455 double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1456 wp->y += fracTop * heightInc;
1457 heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1459 if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1461 wp->height += heightInc;
1462 } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1463 int widthInc = wpNew.width - wpMain.width;
1464 double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1465 double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1466 wp->y += fracLeft * widthInc;
1467 widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1469 if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1471 wp->width += widthInc;
1473 wp->x += wpNew.x - wpMain.x;
1474 wp->y += wpNew.y - wpMain.y;
1475 if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1476 if(touch == 3) wp->y += wpNew.height - wpMain.height;
1478 XtSetArg(args[j], XtNx, wp->x); j++;
1479 XtSetArg(args[j], XtNy, wp->y); j++;
1480 XtSetValues(sh, args, j);
1482 gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1483 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1484 gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1488 ReSize (WindowPlacement *wp)
1491 int sqx, sqy, w, h, hc, lg = lineGap;
1492 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1493 hc = a.height; // clock height can depend on single / double line clock text!
1494 if(clockKludge == a.height) return; // wait for clock to get final size at startup
1495 if(clockKludge) { // clock height OK now; calculate desired initial board height
1497 wp->height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1499 if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1500 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1501 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1502 if(sqy < sqx) sqx = sqy;
1503 if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1504 lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1505 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1506 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1507 if(sqy < sqx) sqx = sqy;
1509 if(sqx != squareSize) {
1510 //printf("new sq size %d (%dx%d)\n", sqx, wp->width, wp->height);
1511 squareSize = sqx; // adopt new square size
1512 CreatePNGPieces(); // make newly scaled pieces
1513 InitDrawingSizes(0, 0); // creates grid etc.
1514 } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1515 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1516 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1517 if(optList[W_BOARD].max > w) optList[W_BOARD].max = w;
1518 if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1521 static guint delayedDragTag = 0;
1530 // GetActualPlacement(shellWidget, &wpNew);
1531 if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1532 wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1533 busy = 0; return; // false alarm
1536 if(appData.useStickyWindows) {
1537 if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1538 if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1539 if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1540 if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1543 DrawPosition(True, NULL);
1544 if(delayedDragTag) g_source_remove(delayedDragTag);
1545 delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1552 //printf("old timr = %d\n", delayedDragTag);
1553 if(delayedDragTag) g_source_remove(delayedDragTag);
1554 delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1555 //printf("new timr = %d\n", delayedDragTag);
1559 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1561 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1563 wpNew.x = event->configure.x;
1564 wpNew.y = event->configure.y;
1565 wpNew.width = event->configure.width;
1566 wpNew.height = event->configure.height;
1567 DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1573 /* Disable all user input other than deleting the window */
1574 static int frozen = 0;
1580 /* Grab by a widget that doesn't accept input */
1581 gtk_grab_add(optList[W_MESSG].handle);
1585 /* Undo a FreezeUI */
1589 if (!frozen) return;
1590 gtk_grab_remove(optList[W_MESSG].handle);
1597 static int oldPausing = FALSE;
1598 static GameMode oldmode = (GameMode) -1;
1600 if (!boardWidget) return;
1602 if (pausing != oldPausing) {
1603 oldPausing = pausing;
1604 MarkMenuItem("Mode.Pause", pausing);
1606 if (appData.showButtonBar) {
1607 /* Always toggle, don't set. Previous code messes up when
1608 invoked while the button is pressed, as releasing it
1609 toggles the state again. */
1611 gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1612 gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1616 wname = ModeToWidgetName(oldmode);
1617 if (wname != NULL) {
1618 MarkMenuItem(wname, False);
1620 wname = ModeToWidgetName(gameMode);
1621 if (wname != NULL) {
1622 MarkMenuItem(wname, True);
1625 MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1627 /* Maybe all the enables should be handled here, not just this one */
1628 EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1630 DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1635 * Button/menu procedures
1638 void CopyFileToClipboard(gchar *filename)
1640 gchar *selection_tmp;
1644 FILE* f = fopen(filename, "r");
1647 if (f == NULL) return;
1651 selection_tmp = g_try_malloc(len + 1);
1652 if (selection_tmp == NULL) {
1653 printf("Malloc failed in CopyFileToClipboard\n");
1656 count = fread(selection_tmp, 1, len, f);
1659 g_free(selection_tmp);
1662 selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1664 // copy selection_tmp to clipboard
1665 GdkDisplay *gdisp = gdk_display_get_default();
1667 g_free(selection_tmp);
1670 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1671 gtk_clipboard_set_text(cb, selection_tmp, -1);
1672 g_free(selection_tmp);
1676 CopySomething (char *src)
1678 GdkDisplay *gdisp = gdk_display_get_default();
1680 if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1681 if (gdisp == NULL) return;
1682 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1683 gtk_clipboard_set_text(cb, src, -1);
1687 PastePositionProc ()
1689 GdkDisplay *gdisp = gdk_display_get_default();
1693 if (gdisp == NULL) return;
1694 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1695 fenstr = gtk_clipboard_wait_for_text(cb);
1696 if (fenstr==NULL) return; // nothing had been selected to copy
1697 EditPositionPasteFEN(fenstr);
1709 // get game from clipboard
1710 GdkDisplay *gdisp = gdk_display_get_default();
1711 if (gdisp == NULL) return;
1712 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1713 text = gtk_clipboard_wait_for_text(cb);
1714 if (text == NULL) return; // nothing to paste
1717 // write to temp file
1718 if (text == NULL || len == 0) {
1719 return; //nothing to paste
1721 f = fopen(gamePasteFilename, "w");
1723 DisplayError(_("Can't open temp file"), errno);
1726 fwrite(text, 1, len, f);
1730 LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1737 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1743 void MoveTypeInProc(eventkey)
1744 GdkEventKey *eventkey;
1748 // ingnore if ctrl, alt, or meta is pressed
1749 if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1753 buf[0]=eventkey->keyval;
1755 if (eventkey->keyval > 32 && eventkey->keyval < 256)
1761 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1763 if (!TempBackwardActive) {
1764 TempBackwardActive = True;
1770 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1772 /* Check to see if triggered by a key release event for a repeating key.
1773 * If so the next queued event will be a key press of the same key at the same time */
1774 if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1776 XPeekEvent(xDisplay, &next);
1777 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1778 next.xkey.keycode == event->xkey.keycode)
1782 TempBackwardActive = False;
1788 { // called from menu
1790 system("%s ./man.command", appData.sysOpen);
1792 system("xterm -e man xboard &");
1797 SetWindowTitle (char *text, char *title, char *icon)
1802 if (appData.titleInWindow) {
1804 XtSetArg(args[i], XtNlabel, text); i++;
1805 XtSetValues(titleWidget, args, i);
1808 XtSetArg(args[i], XtNiconName, (XtArgVal) icon); i++;
1809 XtSetArg(args[i], XtNtitle, (XtArgVal) title); i++;
1810 XtSetValues(shellWidget, args, i);
1811 XSync(xDisplay, False);
1813 if (appData.titleInWindow) {
1814 SetWidgetLabel(titleWidget, text);
1816 gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1821 DisplayIcsInteractionTitle (String message)
1824 if (oldICSInteractionTitle == NULL) {
1825 /* Magic to find the old window title, adapted from vim */
1826 char *wina = getenv("WINDOWID");
1828 Window win = (Window) atoi(wina);
1829 Window root, parent, *children;
1830 unsigned int nchildren;
1831 int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
1833 if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
1834 if (!XQueryTree(xDisplay, win, &root, &parent,
1835 &children, &nchildren)) break;
1836 if (children) XFree((void *)children);
1837 if (parent == root || parent == 0) break;
1840 XSetErrorHandler(oldHandler);
1842 if (oldICSInteractionTitle == NULL) {
1843 oldICSInteractionTitle = "xterm";
1846 printf("\033]0;%s\007", message);
1853 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
1855 GtkWidget *w = (GtkWidget *) opt->handle;
1862 strcpy(bgcolor, "black");
1863 strcpy(fgcolor, "white");
1865 strcpy(bgcolor, "white");
1866 strcpy(fgcolor, "black");
1869 appData.lowTimeWarning &&
1870 (timer / 1000) < appData.icsAlarmTime) {
1871 strcpy(fgcolor, appData.lowTimeWarningColor);
1874 gdk_color_parse( bgcolor, &col );
1875 gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
1877 if (appData.clockMode) {
1878 markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
1879 bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
1881 markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s </span>",
1882 bgcolor, fgcolor, color);
1884 gtk_label_set_markup(GTK_LABEL(w), markup);
1888 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
1891 SetClockIcon (int color)
1893 GdkPixbuf *pm = *clockIcons[color];
1894 if (mainwindowIcon != pm) {
1895 mainwindowIcon = pm;
1896 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1900 #define INPUT_SOURCE_BUF_SIZE 8192
1909 char buf[INPUT_SOURCE_BUF_SIZE];
1914 DoInputCallback(io, cond, data)
1919 /* read input from one of the input source (for example a chess program, ICS, etc).
1920 * and call a function that will handle the input
1927 /* All information (callback function, file descriptor, etc) is
1928 * saved in an InputSource structure
1930 InputSource *is = (InputSource *) data;
1932 if (is->lineByLine) {
1933 count = read(is->fd, is->unused,
1934 INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
1936 (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
1939 is->unused += count;
1941 /* break input into lines and call the callback function on each
1944 while (p < is->unused) {
1945 q = memchr(p, '\n', is->unused - p);
1946 if (q == NULL) break;
1948 (is->func)(is, is->closure, p, q - p, 0);
1951 /* remember not yet used part of the buffer */
1953 while (p < is->unused) {
1958 /* read maximum length of input buffer and send the whole buffer
1959 * to the callback function
1961 count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
1966 (is->func)(is, is->closure, is->buf, count, error);
1968 return True; // Must return true or the watch will be removed
1971 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
1978 GIOChannel *channel;
1979 ChildProc *cp = (ChildProc *) pr;
1981 is = (InputSource *) calloc(1, sizeof(InputSource));
1982 is->lineByLine = lineByLine;
1986 is->fd = fileno(stdin);
1988 is->kind = cp->kind;
1989 is->fd = cp->fdFrom;
1992 is->unused = is->buf;
1996 /* GTK-TODO: will this work on windows?*/
1998 channel = g_io_channel_unix_new(is->fd);
1999 g_io_channel_set_close_on_unref (channel, TRUE);
2000 is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2002 is->closure = closure;
2003 return (InputSourceRef) is;
2008 RemoveInputSource(isr)
2011 InputSource *is = (InputSource *) isr;
2013 if (is->sid == 0) return;
2014 g_source_remove(is->sid);
2021 static Boolean frameWaiting;
2024 FrameAlarm (int sig)
2026 frameWaiting = False;
2027 /* In case System-V style signals. Needed?? */
2028 signal(SIGALRM, FrameAlarm);
2032 FrameDelay (int time)
2034 struct itimerval delay;
2037 frameWaiting = True;
2038 signal(SIGALRM, FrameAlarm);
2039 delay.it_interval.tv_sec =
2040 delay.it_value.tv_sec = time / 1000;
2041 delay.it_interval.tv_usec =
2042 delay.it_value.tv_usec = (time % 1000) * 1000;
2043 setitimer(ITIMER_REAL, &delay, NULL);
2044 while (frameWaiting) pause();
2045 delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2046 delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2047 setitimer(ITIMER_REAL, &delay, NULL);
2054 FrameDelay (int time)
2057 XSync(xDisplay, False);
2059 // gtk_main_iteration_do(False);
2062 usleep(time * 1000);
2068 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2070 char buf[MSG_SIZ], *logoName = buf;
2071 if(appData.logo[n][0]) {
2072 logoName = appData.logo[n];
2073 } else if(appData.autoLogo) {
2074 if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2075 sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2076 } else if(appData.directory[n] && appData.directory[n][0]) {
2077 sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2081 { ASSIGN(cps->programLogo, logoName); }
2085 UpdateLogos (int displ)
2087 if(optList[W_WHITE-1].handle == NULL) return;
2088 LoadLogo(&first, 0, 0);
2089 LoadLogo(&second, 1, appData.icsActive);
2090 if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2094 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2105 GtkFileFilter *gtkfilter;
2106 GtkFileFilter *gtkfilter_all;
2108 char fileext[10] = "";
2109 char *result = NULL;
2112 /* make a copy of the filter string, so that strtok can work with it*/
2113 cp = strdup(filter);
2115 /* add filters for file extensions */
2116 gtkfilter = gtk_file_filter_new();
2117 gtkfilter_all = gtk_file_filter_new();
2119 /* one filter to show everything */
2120 gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2121 gtk_file_filter_set_name (gtkfilter_all, "All Files");
2123 /* add filter if present */
2124 result = strtok(cp, space);
2125 while( result != NULL ) {
2126 snprintf(fileext,10,"*%s",result);
2127 result = strtok( NULL, space );
2128 gtk_file_filter_add_pattern(gtkfilter, fileext);
2131 /* second filter to only show what's useful */
2132 gtk_file_filter_set_name (gtkfilter,filter);
2134 if (openMode[0] == 'r')
2136 dialog = gtk_file_chooser_dialog_new (label,
2138 GTK_FILE_CHOOSER_ACTION_OPEN,
2139 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2140 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2145 dialog = gtk_file_chooser_dialog_new (label,
2147 GTK_FILE_CHOOSER_ACTION_SAVE,
2148 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2149 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2151 /* add filename suggestions */
2152 if (strlen(def) > 0 )
2153 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2155 //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2159 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2160 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2161 /* activate filter */
2162 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2164 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2169 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2172 f = fopen(filename, openMode);
2175 DisplayError(_("Failed to open file"), errno);
2179 /* TODO add indec */
2181 ASSIGN(*name, filename);
2182 ScheduleDelayedEvent(DelayedLoad, 50);
2187 gtk_widget_destroy (dialog);