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, 2014 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
178 # undef SETTINGS_FILE
179 # define ICS_LOGON "Library/Preferences/XboardICS.conf"
180 # define DATADIR dataDir
181 # define SETTINGS_FILE masterSettings
182 char dataDir[MSG_SIZ]; // for expanding ~~
183 char masterSettings[MSG_SIZ];
192 #define usleep(t) _sleep2(((t)+500)/1000)
196 # define _(s) gettext (s)
197 # define N_(s) gettext_noop (s)
203 int main P((int argc, char **argv));
204 RETSIGTYPE CmailSigHandler P((int sig));
205 RETSIGTYPE IntSigHandler P((int sig));
206 RETSIGTYPE TermSizeSigHandler P((int sig));
207 char *InsertPxlSize P((char *pattern, int targetPxlSize));
209 XFontSet CreateFontSet P((char *base_fnt_lst));
211 char *FindFont P((char *pattern, int targetPxlSize));
213 void DelayedDrag P((void));
214 void ICSInputBoxPopUp P((void));
215 void MoveTypeInProc P((GdkEventKey *eventkey));
216 gboolean KeyPressProc P((GtkWindow *window, GdkEventKey *eventkey, gpointer data));
217 Boolean TempBackwardActive = False;
218 void DisplayMove P((int moveNumber));
219 void update_ics_width P(());
220 int CopyMemoProc P(());
221 static gboolean EventProc P((GtkWidget *widget, GdkEvent *event, gpointer g));
225 XFontSet fontSet, clockFontSet;
228 XFontStruct *clockFontStruct;
230 Font coordFontID, countFontID;
231 XFontStruct *coordFontStruct, *countFontStruct;
233 void *shellWidget, *formWidget, *boardWidget, *titleWidget, *dropMenu, *menuBarWidget;
234 GtkWidget *mainwindow;
236 Option *optList; // contains all widgets of main window
239 char installDir[] = "."; // [HGM] UCI: needed for UCI; probably needs run-time initializtion
242 static GdkPixbuf *mainwindowIcon=NULL;
243 static GdkPixbuf *WhiteIcon=NULL;
244 static GdkPixbuf *BlackIcon=NULL;
246 /* key board accelerators */
247 GtkAccelGroup *GtkAccelerators;
249 typedef unsigned int BoardSize;
251 Boolean chessProgram;
253 int minX, minY; // [HGM] placement: volatile limits on upper-left corner
254 int smallLayout = 0, tinyLayout = 0,
255 marginW, marginH, // [HGM] for run-time resizing
256 fromX = -1, fromY = -1, toX, toY, commentUp = False,
257 errorExitStatus = -1, defaultLineGap;
259 Dimension textHeight;
261 char *chessDir, *programName, *programVersion;
262 Boolean alwaysOnTop = False;
263 char *icsTextMenuString;
265 char *firstChessProgramNames;
266 char *secondChessProgramNames;
268 WindowPlacement wpMain;
269 WindowPlacement wpConsole;
270 WindowPlacement wpComment;
271 WindowPlacement wpMoveHistory;
272 WindowPlacement wpEvalGraph;
273 WindowPlacement wpEngineOutput;
274 WindowPlacement wpGameList;
275 WindowPlacement wpTags;
276 WindowPlacement wpDualBoard;
278 /* This magic number is the number of intermediate frames used
279 in each half of the animation. For short moves it's reduced
280 by 1. The total number of frames will be factor * 2 + 1. */
283 SizeDefaults sizeDefaults[] = SIZE_DEFAULTS;
290 DropMenuEnables dmEnables[] = {
299 XtResource clientResources[] = {
300 { "flashCount", "flashCount", XtRInt, sizeof(int),
301 XtOffset(AppDataPtr, flashCount), XtRImmediate,
302 (XtPointer) FLASH_COUNT },
306 /* keyboard shortcuts not yet transistioned int menuitem @ menu.c */
307 char globalTranslations[] =
308 ":Ctrl<Key>Down: LoadSelectedProc(3) \n \
309 :Ctrl<Key>Up: LoadSelectedProc(-3) \n \
310 :<KeyDown>Return: TempBackwardProc() \n \
311 :<KeyUp>Return: TempForwardProc() \n";
313 char ICSInputTranslations[] =
314 "<Key>Up: UpKeyProc() \n "
315 "<Key>Down: DownKeyProc() \n "
316 "<Key>Return: EnterKeyProc() \n";
318 // [HGM] vari: another hideous kludge: call extend-end first so we can be sure select-start works,
319 // as the widget is destroyed before the up-click can call extend-end
320 char commentTranslations[] = "<Btn3Down>: extend-end() select-start() CommentClick() \n";
323 String xboardResources[] = {
324 "*Error*translations: #override\\n <Key>Return: ErrorPopDown()",
332 gtk_window_present(GTK_WINDOW(shells[BoardWindow]));
335 //---------------------------------------------------------------------------------------------------------
336 // some symbol definitions to provide the proper (= XBoard) context for the code in args.h
339 #define CW_USEDEFAULT (1<<31)
340 #define ICS_TEXT_MENU_SIZE 90
341 #define DEBUG_FILE "xboard.debug"
342 #define SetCurrentDirectory chdir
343 #define GetCurrentDirectory(SIZE, NAME) getcwd(NAME, SIZE)
347 // The option definition and parsing code common to XBoard and WinBoard is collected in this file
350 // front-end part of option handling
352 // [HGM] This platform-dependent table provides the location for storing the color info
353 extern char *crWhite, * crBlack;
357 &appData.whitePieceColor,
358 &appData.blackPieceColor,
359 &appData.lightSquareColor,
360 &appData.darkSquareColor,
361 &appData.highlightSquareColor,
362 &appData.premoveHighlightColor,
363 &appData.lowTimeWarningColor,
374 // [HGM] font: keep a font for each square size, even non-stndard ones
377 Boolean fontIsSet[NUM_FONTS], fontValid[NUM_FONTS][MAX_SIZE];
378 char *fontTable[NUM_FONTS][MAX_SIZE];
381 ParseFont (char *name, int number)
382 { // in XBoard, only 2 of the fonts are currently implemented, and we just copy their name
384 if(sscanf(name, "size%d:", &size)) {
385 // [HGM] font: font is meant for specific boardSize (likely from settings file);
386 // defer processing it until we know if it matches our board size
387 if(!strstr(name, "-*-") && // ignore X-fonts
388 size >= 0 && size<MAX_SIZE) { // for now, fixed limit
389 fontTable[number][size] = strdup(strchr(name, ':')+1);
390 fontValid[number][size] = True;
395 case 0: // CLOCK_FONT
396 appData.clockFont = strdup(name);
398 case 1: // MESSAGE_FONT
399 appData.font = strdup(name);
401 case 2: // COORD_FONT
402 appData.coordFont = strdup(name);
407 fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
412 { // only 2 fonts currently
413 appData.clockFont = CLOCK_FONT_NAME;
414 appData.coordFont = COORD_FONT_NAME;
415 appData.font = DEFAULT_FONT_NAME;
420 { // no-op, until we identify the code for this already in XBoard and move it here
424 ParseColor (int n, char *name)
425 { // in XBoard, just copy the color-name string
426 if(colorVariable[n]) *(char**)colorVariable[n] = strdup(name);
432 return *(char**)colorVariable[n];
436 ParseTextAttribs (ColorClass cc, char *s)
438 (&appData.colorShout)[cc] = strdup(s);
442 ParseBoardSize (void *addr, char *name)
444 appData.boardSize = strdup(name);
449 { // In XBoard the sound-playing program takes care of obtaining the actual sound
453 SetCommPortDefaults ()
454 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
457 // [HGM] args: these three cases taken out to stay in front-end
459 SaveFontArg (FILE *f, ArgDescriptor *ad)
462 int i, n = (int)(intptr_t)ad->argLoc;
464 case 0: // CLOCK_FONT
465 name = appData.clockFont;
467 case 1: // MESSAGE_FONT
470 case 2: // COORD_FONT
471 name = appData.coordFont;
476 for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
477 if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
478 fontTable[n][squareSize] = strdup(name);
479 fontValid[n][squareSize] = True;
482 for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
483 fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
488 { // nothing to do, as the sounds are at all times represented by their text-string names already
492 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
493 { // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
494 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
498 SaveColor (FILE *f, ArgDescriptor *ad)
499 { // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
500 if(colorVariable[(int)(intptr_t)ad->argLoc])
501 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
505 SaveBoardSize (FILE *f, char *name, void *addr)
506 { // wrapper to shield back-end from BoardSize & sizeInfo
507 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
511 ParseCommPortSettings (char *s)
512 { // no such option in XBoard (yet)
518 GetActualPlacement (GtkWidget *shell, WindowPlacement *wp)
522 gtk_widget_get_allocation(shell, &a);
523 gtk_window_get_position(GTK_WINDOW(shell), &a.x, &a.y);
527 wp->height = a.height;
528 //printf("placement: (%d,%d) %dx%d\n", a.x, a.y, a.width, a.height);
529 frameX = 3; frameY = 3; // remember to decide if windows touch
533 GetPlacement (DialogClass dlg, WindowPlacement *wp)
534 { // wrapper to shield back-end from widget type
535 if(shellUp[dlg]) GetActualPlacement(shells[dlg], wp);
540 { // wrapper to shield use of window handles from back-end (make addressible by number?)
541 // In XBoard this will have to wait until awareness of window parameters is implemented
542 GetActualPlacement(shellWidget, &wpMain);
543 if(shellUp[EngOutDlg]) GetActualPlacement(shells[EngOutDlg], &wpEngineOutput);
544 if(shellUp[HistoryDlg]) GetActualPlacement(shells[HistoryDlg], &wpMoveHistory);
545 if(shellUp[EvalGraphDlg]) GetActualPlacement(shells[EvalGraphDlg], &wpEvalGraph);
546 if(shellUp[GameListDlg]) GetActualPlacement(shells[GameListDlg], &wpGameList);
547 if(shellUp[CommentDlg]) GetActualPlacement(shells[CommentDlg], &wpComment);
548 if(shellUp[TagsDlg]) GetActualPlacement(shells[TagsDlg], &wpTags);
549 GetPlacement(ChatDlg, &wpConsole); if(appData.icsActive) wpConsole.visible = shellUp[ChatDlg];
553 PrintCommPortSettings (FILE *f, char *name)
554 { // This option does not exist in XBoard
558 EnsureOnScreen (int *x, int *y, int minX, int minY)
565 { // [HGM] args: allows testing if main window is realized from back-end
566 return DialogExists(BoardWindow);
570 PopUpStartupDialog ()
571 { // start menu not implemented in XBoard
575 ConvertToLine (int argc, char **argv)
577 static char line[128*1024], buf[1024];
581 for(i=1; i<argc; i++)
583 if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
584 && argv[i][0] != '{' )
585 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
587 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
588 strncat(line, buf, 128*1024 - strlen(line) - 1 );
591 line[strlen(line)-1] = NULLCHAR;
595 //--------------------------------------------------------------------------------------------
600 ResizeBoardWindow (int w, int h, int inhibit)
603 // if(clockKludge) return; // ignore as long as clock does not have final height
604 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
605 w += marginW + 1; // [HGM] not sure why the +1 is (sometimes) needed...
606 h += marginH + a.height + 1;
607 gtk_window_resize(GTK_WINDOW(shellWidget), w, h);
612 { // dummy, as the GTK code does not make colors in advance
617 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
618 { // determine what fonts to use, and create them
620 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
621 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
622 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
623 appData.font = fontTable[MESSAGE_FONT][squareSize];
624 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
625 appData.coordFont = fontTable[COORD_FONT][squareSize];
627 appData.font = InsertPxlSize(appData.font, fontPxlSize);
628 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
629 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
635 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
636 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
637 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
638 appData.font = fontTable[MESSAGE_FONT][squareSize];
639 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
640 appData.coordFont = fontTable[COORD_FONT][squareSize];
643 appData.font = InsertPxlSize(appData.font, fontPxlSize);
644 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
645 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
646 fontSet = CreateFontSet(appData.font);
647 clockFontSet = CreateFontSet(appData.clockFont);
649 /* For the coordFont, use the 0th font of the fontset. */
650 XFontSet coordFontSet = CreateFontSet(appData.coordFont);
651 XFontStruct **font_struct_list;
652 XFontSetExtents *fontSize;
653 char **font_name_list;
654 XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
655 coordFontID = XLoadFont(xDisplay, font_name_list[0]);
656 coordFontStruct = XQueryFont(xDisplay, coordFontID);
657 fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
658 textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
661 appData.font = FindFont(appData.font, fontPxlSize);
662 appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
663 appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
664 clockFontID = XLoadFont(xDisplay, appData.clockFont);
665 clockFontStruct = XQueryFont(xDisplay, clockFontID);
666 coordFontID = XLoadFont(xDisplay, appData.coordFont);
667 coordFontStruct = XQueryFont(xDisplay, coordFontID);
668 // textHeight in !NLS mode!
670 countFontID = coordFontID; // [HGM] holdings
671 countFontStruct = coordFontStruct;
673 xdb = XtDatabase(xDisplay);
675 XrmPutLineResource(&xdb, "*international: True");
676 vTo.size = sizeof(XFontSet);
677 vTo.addr = (XtPointer) &fontSet;
678 XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
680 XrmPutStringResource(&xdb, "*font", appData.font);
691 case ArgInt: p = " N"; break;
692 case ArgString: p = " STR"; break;
693 case ArgBoolean: p = " TF"; break;
694 case ArgSettingsFilename:
695 case ArgBackupSettingsFile:
696 case ArgFilename: p = " FILE"; break;
697 case ArgX: p = " Nx"; break;
698 case ArgY: p = " Ny"; break;
699 case ArgAttribs: p = " TEXTCOL"; break;
700 case ArgColor: p = " COL"; break;
701 case ArgFont: p = " FONT"; break;
702 case ArgBoardSize: p = " SIZE"; break;
703 case ArgFloat: p = " FLOAT"; break;
708 case ArgCommSettings:
720 ArgDescriptor *q, *p = argDescriptors+5;
721 printf("\nXBoard accepts the following options:\n"
722 "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
723 " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
724 " SIZE = board-size spec(s)\n"
725 " Within parentheses are short forms, or options to set to true or false.\n"
726 " Persistent options (saved in the settings file) are marked with *)\n\n");
728 if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
729 snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
730 if(p->save) strcat(buf+len, "*");
731 for(q=p+1; q->argLoc == p->argLoc; q++) {
732 if(q->argName[0] == '-') continue;
733 strcat(buf+len, q == p+1 ? " (" : " ");
734 sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
736 if(q != p+1) strcat(buf+len, ")");
738 if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
741 if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
745 SlaveResize (Option *opt)
747 static int slaveW, slaveH, w, h;
750 gtk_widget_get_allocation(shells[DummyDlg], &a);
751 w = a.width; h = a.height;
752 gtk_widget_get_allocation(opt->handle, &a);
753 slaveW = w - opt->max; // [HGM] needed to set new shellWidget size when we resize board
754 slaveH = h - a.height + 13;
756 gtk_window_resize(GTK_WINDOW(shells[DummyDlg]), slaveW + opt->max, slaveH + opt->value);
760 static char clickedFile[MSG_SIZ];
764 StartNewXBoard(GtkosxApplication *app, gchar *path, gpointer user_data)
765 { // handler of OSX OpenFile signal, which sends us the filename of clicked file or first argument
766 if(suppress) { // we just started XBoard without arguments
767 strncpy(clickedFile, path, MSG_SIZ); // remember file name, but otherwise ignore
768 } else { // we are running something presumably useful
770 snprintf(buf, MSG_SIZ, "open -n -a \"xboard\" --args \"%s\"", path);
771 system(buf); // start new instance on this file
778 main (int argc, char **argv)
780 int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
781 int boardWidth, w, h; //, boardHeight;
783 int forceMono = False;
785 srandom(time(0)); // [HGM] book: make random truly random
787 setbuf(stdout, NULL);
788 setbuf(stderr, NULL);
791 if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
792 printf("%s version %s\n\n configure options: %s\n", PACKAGE_NAME, PACKAGE_VERSION, CONFIGURE_OPTIONS);
796 if(argc > 1 && !strcmp(argv[1], "--help" )) {
802 gtk_init (&argc, &argv);
804 { // prepare to catch OX OpenFile signal, which will tell us the clicked file
805 GtkosxApplication *theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
806 char *path = gtkosx_application_get_bundle_path();
807 strncpy(dataDir, path, MSG_SIZ);
808 snprintf(masterSettings, MSG_SIZ, "%s/Contents/Resources/etc/xboard.conf", path);
809 g_signal_connect(theApp, "NSApplicationOpenFile", G_CALLBACK(StartNewXBoard), NULL);
810 // we must call application ready before we can get the signal,
811 // and supply a (dummy) menu bar before that, to avoid problems with dual apples in it
812 gtkosx_application_set_menu_bar(theApp, GTK_MENU_SHELL(gtk_menu_bar_new()));
813 gtkosx_application_ready(theApp);
814 suppress = (argc == 1 || argc > 1 && argv[1][00] != '-'); // OSX sends signal even if name was already argv[1]!
815 if(argc == 1) { // called without args: OSX open-file signal might follow
816 static char *fakeArgv[3] = {NULL, clickedFile, NULL};
817 usleep(10000); // wait 10 msec (and hope this is long enough).
818 while(gtk_events_pending())
819 gtk_main_iteration(); // process all events that came in upto now
820 suppress = 0; // future open-file signals should start new instance
821 if(clickedFile[0]) { // we were sent an open-file signal with filename!
822 fakeArgv[0] = argv[0];
823 argc = 2; argv = fakeArgv; // fake that we were called as "xboard filename"
829 if(argc > 1 && !strcmp(argv[1], "--show-config")) { // [HGM] install: called to print config info
830 typedef struct {char *name, *value; } Config;
831 static Config configList[] = {
832 { "Datadir", DATADIR },
833 { "Sysconfdir", SYSCONFDIR },
838 for(i=0; configList[i].name; i++) {
839 if(argc > 2 && strcmp(argv[2], configList[i].name)) continue;
840 if(argc > 2) printf("%s", configList[i].value);
841 else printf("%-12s: %s\n", configList[i].name, configList[i].value);
846 /* set up keyboard accelerators group */
847 GtkAccelerators = gtk_accel_group_new();
849 programName = strrchr(argv[0], '/');
850 if (programName == NULL)
851 programName = argv[0];
856 // if (appData.debugMode) {
857 // fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
860 bindtextdomain(PACKAGE, LOCALEDIR);
861 bind_textdomain_codeset(PACKAGE, "UTF-8"); // needed when creating markup for the clocks
865 appData.boardSize = "";
866 InitAppData(ConvertToLine(argc, argv));
868 if (p == NULL) p = "/tmp";
869 i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
870 gameCopyFilename = (char*) malloc(i);
871 gamePasteFilename = (char*) malloc(i);
872 snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
873 snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
875 { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
876 static char buf[MSG_SIZ];
877 snprintf(buf, MSG_SIZ, appData.sysOpen, DATADIR);
878 ASSIGN(appData.sysOpen, buf); // expand %s in -openCommand to DATADIR (usefull for OS X configuring)
879 EscapeExpand(buf, appData.firstInitString);
880 appData.firstInitString = strdup(buf);
881 EscapeExpand(buf, appData.secondInitString);
882 appData.secondInitString = strdup(buf);
883 EscapeExpand(buf, appData.firstComputerString);
884 appData.firstComputerString = strdup(buf);
885 EscapeExpand(buf, appData.secondComputerString);
886 appData.secondComputerString = strdup(buf);
889 if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
892 if (chdir(chessDir) != 0) {
893 fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
899 if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
900 /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
901 if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL) {
902 printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
905 setbuf(debugFP, NULL);
909 if (appData.debugMode) {
910 fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
914 /* [HGM,HR] make sure board size is acceptable */
915 if(appData.NrFiles > BOARD_FILES ||
916 appData.NrRanks > BOARD_RANKS )
917 DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
920 /* This feature does not work; animation needs a rewrite */
921 appData.highlightDragging = FALSE;
925 gameInfo.variant = StringToVariant(appData.variant);
929 * determine size, based on supplied or remembered -size, or screen size
931 if (isdigit(appData.boardSize[0])) {
932 i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
933 &lineGap, &clockFontPxlSize, &coordFontPxlSize,
934 &fontPxlSize, &smallLayout, &tinyLayout);
936 fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
937 programName, appData.boardSize);
941 squareSize = (squareSize*8 + BOARD_WIDTH/2)/BOARD_WIDTH; // scale height
943 /* Find some defaults; use the nearest known size */
944 SizeDefaults *szd, *nearest;
945 int distance = 99999;
946 nearest = szd = sizeDefaults;
947 while (szd->name != NULL) {
948 if (abs(szd->squareSize - squareSize) < distance) {
950 distance = abs(szd->squareSize - squareSize);
951 if (distance == 0) break;
955 if (i < 2) lineGap = nearest->lineGap;
956 if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
957 if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
958 if (i < 5) fontPxlSize = nearest->fontPxlSize;
959 if (i < 6) smallLayout = nearest->smallLayout;
960 if (i < 7) tinyLayout = nearest->tinyLayout;
963 SizeDefaults *szd = sizeDefaults;
964 if (*appData.boardSize == NULLCHAR) {
965 GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow)); // TODO: this does not work, as no mainwindow yet
966 guint screenwidth = gdk_screen_get_width(screen);
967 guint screenheight = gdk_screen_get_height(screen);
968 while (screenwidth < (szd->minScreenSize*BOARD_WIDTH + 4)/8 ||
969 screenheight < (szd->minScreenSize*BOARD_HEIGHT + 4)/8) {
972 if (szd->name == NULL) szd--;
973 appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
975 while (szd->name != NULL &&
976 StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
977 if (szd->name == NULL) {
978 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
979 programName, appData.boardSize);
983 squareSize = szd->squareSize;
984 lineGap = szd->lineGap;
985 clockFontPxlSize = szd->clockFontPxlSize;
986 coordFontPxlSize = szd->coordFontPxlSize;
987 fontPxlSize = szd->fontPxlSize;
988 smallLayout = szd->smallLayout;
989 tinyLayout = szd->tinyLayout;
990 // [HGM] font: use defaults from settings file if available and not overruled
993 defaultLineGap = lineGap;
994 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
996 /* [HR] height treated separately (hacked) */
997 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
998 // boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1001 * Determine what fonts to use.
1003 InitializeFonts((2*clockFontPxlSize+1)/3, coordFontPxlSize, fontPxlSize);
1006 * Detect if there are not enough colors available and adapt.
1009 if (DefaultDepth(xDisplay, xScreen) <= 2) {
1010 appData.monoMode = True;
1014 forceMono = MakeColors();
1017 fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1019 appData.monoMode = True;
1022 ParseIcsTextColors();
1028 layoutName = "tinyLayout";
1029 } else if (smallLayout) {
1030 layoutName = "smallLayout";
1032 layoutName = "normalLayout";
1035 wpMain.width = -1; // prevent popup sizes window
1036 optList = BoardPopUp(squareSize, lineGap, (void*)
1046 InitDrawingHandle(optList + W_BOARD);
1047 shellWidget = shells[BoardWindow];
1048 currBoard = &optList[W_BOARD];
1049 boardWidget = optList[W_BOARD].handle;
1050 menuBarWidget = optList[W_MENU].handle;
1051 dropMenu = optList[W_DROP].handle;
1052 titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1054 formWidget = XtParent(boardWidget);
1055 XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1056 XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1057 XtGetValues(optList[W_WHITE].handle, args, 2);
1058 if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1059 XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1060 XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1061 XtGetValues(optList[W_PAUSE].handle, args, 2);
1065 // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1066 // not need to go into InitDrawingSizes().
1070 // add accelerators to main shell
1071 gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1074 * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1076 WhiteIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_white.svg", NULL);
1077 BlackIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_black.svg", NULL);
1078 mainwindowIcon = WhiteIcon;
1079 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1083 * Create a cursor for the board widget.
1086 window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1087 XChangeWindowAttributes(xDisplay, xBoardWindow,
1088 CWCursor, &window_attributes);
1092 * Inhibit shell resizing.
1095 shellArgs[0].value = (XtArgVal) &w;
1096 shellArgs[1].value = (XtArgVal) &h;
1097 XtGetValues(shellWidget, shellArgs, 2);
1098 shellArgs[4].value = shellArgs[2].value = w;
1099 shellArgs[5].value = shellArgs[3].value = h;
1100 // XtSetValues(shellWidget, &shellArgs[2], 4);
1103 // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1104 // It wil only become known asynchronously, when we first write a string into it.
1105 // This will then change the clock widget height, which triggers resizing the top-level window
1106 // and a configure event. Only then can we know the total height of the top-level window,
1107 // and calculate the height we need. The clockKludge flag suppresses all resizing until
1108 // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1111 gtk_widget_get_allocation(shells[BoardWindow], &a);
1112 w = a.width; h = a.height;
1113 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1114 clockKludge = hc = a.height;
1115 gtk_widget_get_allocation(boardWidget, &a);
1116 marginW = w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1117 marginH = h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1123 if(appData.logoSize)
1124 { // locate and read user logo
1126 snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1127 ASSIGN(userLogo, buf);
1130 if (appData.animate || appData.animateDragging)
1133 g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1134 g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1136 /* [AS] Restore layout */
1137 if( wpMoveHistory.visible ) {
1141 if( wpEvalGraph.visible )
1146 if( wpEngineOutput.visible ) {
1147 EngineOutputPopUp();
1150 if( wpConsole.visible && appData.icsActive ) {
1155 gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1160 if (errorExitStatus == -1) {
1161 if (appData.icsActive) {
1162 /* We now wait until we see "login:" from the ICS before
1163 sending the logon script (problems with timestamp otherwise) */
1164 /*ICSInitScript();*/
1165 if (appData.icsInputBox) ICSInputBoxPopUp();
1169 signal(SIGWINCH, TermSizeSigHandler);
1171 signal(SIGINT, IntSigHandler);
1172 signal(SIGTERM, IntSigHandler);
1173 if (*appData.cmailGameName != NULLCHAR) {
1174 signal(SIGUSR1, CmailSigHandler);
1179 // XtSetKeyboardFocus(shellWidget, formWidget);
1181 XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1184 /* check for GTK events and process them */
1187 gtk_main_iteration();
1190 if (appData.debugMode) fclose(debugFP); // [DM] debug
1197 while(gtk_events_pending()) gtk_main_iteration();
1201 TermSizeSigHandler (int sig)
1207 IntSigHandler (int sig)
1213 CmailSigHandler (int sig)
1218 signal(SIGUSR1, SIG_IGN); /* suspend handler */
1220 /* Activate call-back function CmailSigHandlerCallBack() */
1221 OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1223 signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1227 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1230 ReloadCmailMsgEvent(TRUE); /* Reload cmail msg */
1232 /**** end signal code ****/
1235 #define Abs(n) ((n)<0 ? -(n) : (n))
1238 InsertPxlSize (char *pattern, int targetPxlSize)
1241 snprintf(buf, MSG_SIZ, pattern, targetPxlSize); // pattern is something like "Sans Bold %d"
1248 InsertPxlSize (char *pattern, int targetPxlSize)
1250 char *base_fnt_lst, strInt[12], *p, *q;
1251 int alternatives, i, len, strIntLen;
1254 * Replace the "*" (if present) in the pixel-size slot of each
1255 * alternative with the targetPxlSize.
1259 while ((p = strchr(p, ',')) != NULL) {
1263 snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1264 strIntLen = strlen(strInt);
1265 base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1269 while (alternatives--) {
1270 char *comma = strchr(p, ',');
1271 for (i=0; i<14; i++) {
1272 char *hyphen = strchr(p, '-');
1274 if (comma && hyphen > comma) break;
1275 len = hyphen + 1 - p;
1276 if (i == 7 && *p == '*' && len == 2) {
1278 memcpy(q, strInt, strIntLen);
1288 len = comma + 1 - p;
1295 return base_fnt_lst;
1301 CreateFontSet (char *base_fnt_lst)
1304 char **missing_list;
1308 fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1309 &missing_list, &missing_count, &def_string);
1310 if (appData.debugMode) {
1312 XFontStruct **font_struct_list;
1313 char **font_name_list;
1314 fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1316 fprintf(debugFP, " got list %s, locale %s\n",
1317 XBaseFontNameListOfFontSet(fntSet),
1318 XLocaleOfFontSet(fntSet));
1319 count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1320 for (i = 0; i < count; i++) {
1321 fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1324 for (i = 0; i < missing_count; i++) {
1325 fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1328 if (fntSet == NULL) {
1329 fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1335 #else // not ENABLE_NLS
1337 * Find a font that matches "pattern" that is as close as
1338 * possible to the targetPxlSize. Prefer fonts that are k
1339 * pixels smaller to fonts that are k pixels larger. The
1340 * pattern must be in the X Consortium standard format,
1341 * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1342 * The return value should be freed with XtFree when no
1347 FindFont (char *pattern, int targetPxlSize)
1349 char **fonts, *p, *best, *scalable, *scalableTail;
1350 int i, j, nfonts, minerr, err, pxlSize;
1352 fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1354 fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1355 programName, pattern);
1362 for (i=0; i<nfonts; i++) {
1365 if (*p != '-') continue;
1367 if (*p == NULLCHAR) break;
1368 if (*p++ == '-') j++;
1370 if (j < 7) continue;
1373 scalable = fonts[i];
1376 err = pxlSize - targetPxlSize;
1377 if (Abs(err) < Abs(minerr) ||
1378 (minerr > 0 && err < 0 && -err == minerr)) {
1384 if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1385 /* If the error is too big and there is a scalable font,
1386 use the scalable font. */
1387 int headlen = scalableTail - scalable;
1388 p = (char *) XtMalloc(strlen(scalable) + 10);
1389 while (isdigit(*scalableTail)) scalableTail++;
1390 sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1392 p = (char *) XtMalloc(strlen(best) + 2);
1393 safeStrCpy(p, best, strlen(best)+1 );
1395 if (appData.debugMode) {
1396 fprintf(debugFP, "resolved %s at pixel size %d\n to %s\n",
1397 pattern, targetPxlSize, p);
1399 XFreeFontNames(fonts);
1406 EnableNamedMenuItem (char *menuRef, int state)
1408 MenuItem *item = MenuNameToItem(menuRef);
1410 if(item && item->handle) gtk_widget_set_sensitive(item->handle, state);
1414 EnableButtonBar (int state)
1417 XtSetSensitive(optList[W_BUTTON].handle, state);
1423 SetMenuEnables (Enables *enab)
1425 while (enab->name != NULL) {
1426 EnableNamedMenuItem(enab->name, enab->value);
1431 gboolean KeyPressProc(window, eventkey, data)
1433 GdkEventKey *eventkey;
1437 MoveTypeInProc(eventkey); // pop up for typed in moves
1440 /* check for other key values */
1441 switch(eventkey->keyval) {
1453 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1454 { // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1456 if(*nprms == 0) return;
1457 item = MenuNameToItem(prms[0]);
1458 if(item) ((MenuProc *) item->proc) ();
1472 for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1473 entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1474 p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1475 dmEnables[i].piece);
1476 XtSetSensitive(entry, p != NULL || !appData.testLegality
1477 /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1478 && !appData.icsActive));
1480 while (p && *p++ == dmEnables[i].piece) count++;
1481 snprintf(label, sizeof(label), "%s %d", dmEnables[i].widget, count);
1483 XtSetArg(args[j], XtNlabel, label); j++;
1484 XtSetValues(entry, args, j);
1490 do_flash_delay (unsigned long msec)
1496 FlashDelay (int flash_delay)
1498 if(flash_delay) do_flash_delay(flash_delay);
1502 Fraction (int x, int start, int stop)
1504 double f = ((double) x - start)/(stop - start);
1505 if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1509 static WindowPlacement wpNew;
1512 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1514 int touch=0, fudge = 2, f = 2;
1515 GetActualPlacement(sh, wp);
1516 if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x) < fudge) touch = 1; else // right touch
1517 if(abs(wp->x + wp->width + 2*frameX + f - wpMain.x) < fudge) touch = 2; else // left touch
1518 if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1519 if(abs(wp->y + wp->height + frameX + frameY + f - wpMain.y) < fudge) touch = 4; // top touch
1520 //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);
1521 if(!touch ) return; // only windows that touch co-move
1522 if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1523 int heightInc = wpNew.height - wpMain.height;
1524 double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1525 double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1526 wp->y += fracTop * heightInc;
1527 heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1529 if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1531 wp->height += heightInc;
1532 } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1533 int widthInc = wpNew.width - wpMain.width;
1534 double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1535 double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1536 wp->y += fracLeft * widthInc;
1537 widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1539 if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1541 wp->width += widthInc;
1543 wp->x += wpNew.x - wpMain.x;
1544 wp->y += wpNew.y - wpMain.y;
1545 if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1546 if(touch == 3) wp->y += wpNew.height - wpMain.height;
1548 XtSetArg(args[j], XtNx, wp->x); j++;
1549 XtSetArg(args[j], XtNy, wp->y); j++;
1550 XtSetValues(sh, args, j);
1552 gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1553 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1554 gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1558 ReSize (WindowPlacement *wp)
1561 int sqx, sqy, w, h, hc, lg = lineGap;
1562 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1563 hc = a.height; // clock height can depend on single / double line clock text!
1564 if(clockKludge && hc != clockKludge) wp->height += hc - clockKludge, clockKludge = 0;
1565 wpMain.height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1566 if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1567 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1568 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1569 if(sqy < sqx) sqx = sqy;
1570 if(sqx < 20) return;
1571 if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1572 lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1573 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1574 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1575 if(sqy < sqx) sqx = sqy;
1577 if(sqx != squareSize) {
1578 squareSize = sqx; // adopt new square size
1579 CreatePNGPieces(); // make newly scaled pieces
1580 InitDrawingSizes(0, 0); // creates grid etc.
1581 } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1582 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1583 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1584 if(optList[W_BOARD].max > w) optList[W_BOARD].max = w;
1585 if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1588 static guint delayedDragTag = 0;
1597 // GetActualPlacement(shellWidget, &wpNew);
1598 if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1599 wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1600 busy = 0; return; // false alarm
1603 if(appData.useStickyWindows) {
1604 if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1605 if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1606 if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1607 if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1608 if(shellUp[ChatDlg]) CoDrag(shells[ChatDlg], &wpConsole);
1611 DrawPosition(True, NULL);
1612 if(delayedDragTag) g_source_remove(delayedDragTag);
1613 delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1620 //printf("old timr = %d\n", delayedDragTag);
1621 if(delayedDragTag) g_source_remove(delayedDragTag);
1622 delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1623 //printf("new timr = %d\n", delayedDragTag);
1627 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1629 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1631 wpNew.x = event->configure.x;
1632 wpNew.y = event->configure.y;
1633 wpNew.width = event->configure.width;
1634 wpNew.height = event->configure.height;
1635 DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1641 /* Disable all user input other than deleting the window */
1642 static int frozen = 0;
1648 /* Grab by a widget that doesn't accept input */
1649 gtk_grab_add(optList[W_MESSG].handle);
1653 /* Undo a FreezeUI */
1657 if (!frozen) return;
1658 gtk_grab_remove(optList[W_MESSG].handle);
1665 static int oldPausing = FALSE;
1666 static GameMode oldmode = (GameMode) -1;
1668 if (!boardWidget) return;
1670 if (pausing != oldPausing) {
1671 oldPausing = pausing;
1672 MarkMenuItem("Mode.Pause", pausing);
1674 if (appData.showButtonBar) {
1675 /* Always toggle, don't set. Previous code messes up when
1676 invoked while the button is pressed, as releasing it
1677 toggles the state again. */
1679 gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1680 gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1684 wname = ModeToWidgetName(oldmode);
1685 if (wname != NULL) {
1686 MarkMenuItem(wname, False);
1688 wname = ModeToWidgetName(gameMode);
1689 if (wname != NULL) {
1690 MarkMenuItem(wname, True);
1693 MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1695 /* Maybe all the enables should be handled here, not just this one */
1696 EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1698 DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1703 * Button/menu procedures
1706 void CopyFileToClipboard(gchar *filename)
1708 gchar *selection_tmp;
1712 FILE* f = fopen(filename, "r");
1715 if (f == NULL) return;
1719 selection_tmp = g_try_malloc(len + 1);
1720 if (selection_tmp == NULL) {
1721 printf("Malloc failed in CopyFileToClipboard\n");
1724 count = fread(selection_tmp, 1, len, f);
1727 g_free(selection_tmp);
1730 selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1732 // copy selection_tmp to clipboard
1733 GdkDisplay *gdisp = gdk_display_get_default();
1735 g_free(selection_tmp);
1738 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1739 gtk_clipboard_set_text(cb, selection_tmp, -1);
1740 g_free(selection_tmp);
1744 CopySomething (char *src)
1746 GdkDisplay *gdisp = gdk_display_get_default();
1748 if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1749 if (gdisp == NULL) return;
1750 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1751 gtk_clipboard_set_text(cb, src, -1);
1755 PastePositionProc ()
1757 GdkDisplay *gdisp = gdk_display_get_default();
1761 if (gdisp == NULL) return;
1762 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1763 fenstr = gtk_clipboard_wait_for_text(cb);
1764 if (fenstr==NULL) return; // nothing had been selected to copy
1765 EditPositionPasteFEN(fenstr);
1777 // get game from clipboard
1778 GdkDisplay *gdisp = gdk_display_get_default();
1779 if (gdisp == NULL) return;
1780 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1781 text = gtk_clipboard_wait_for_text(cb);
1782 if (text == NULL) return; // nothing to paste
1785 // write to temp file
1786 if (text == NULL || len == 0) {
1787 return; //nothing to paste
1789 f = fopen(gamePasteFilename, "w");
1791 DisplayError(_("Can't open temp file"), errno);
1794 fwrite(text, 1, len, f);
1798 LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1805 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1811 void MoveTypeInProc(eventkey)
1812 GdkEventKey *eventkey;
1816 // ingnore if ctrl, alt, or meta is pressed
1817 if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1821 buf[0]=eventkey->keyval;
1823 if (eventkey->keyval > 32 && eventkey->keyval < 256)
1824 ConsoleAutoPopUp (buf);
1829 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1831 if (!TempBackwardActive) {
1832 TempBackwardActive = True;
1838 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1840 /* Check to see if triggered by a key release event for a repeating key.
1841 * If so the next queued event will be a key press of the same key at the same time */
1842 if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1844 XPeekEvent(xDisplay, &next);
1845 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1846 next.xkey.keycode == event->xkey.keycode)
1850 TempBackwardActive = False;
1856 { // called from menu
1859 snprintf(buf, MSG_SIZ, "%s ./man.command", appData.sysOpen);
1862 system("xterm -e man xboard &");
1867 SetWindowTitle (char *text, char *title, char *icon)
1872 if (appData.titleInWindow) {
1874 XtSetArg(args[i], XtNlabel, text); i++;
1875 XtSetValues(titleWidget, args, i);
1878 XtSetArg(args[i], XtNiconName, (XtArgVal) icon); i++;
1879 XtSetArg(args[i], XtNtitle, (XtArgVal) title); i++;
1880 XtSetValues(shellWidget, args, i);
1881 XSync(xDisplay, False);
1883 if (appData.titleInWindow) {
1884 SetWidgetLabel(titleWidget, text);
1886 gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1891 DisplayIcsInteractionTitle (String message)
1894 if (oldICSInteractionTitle == NULL) {
1895 /* Magic to find the old window title, adapted from vim */
1896 char *wina = getenv("WINDOWID");
1898 Window win = (Window) atoi(wina);
1899 Window root, parent, *children;
1900 unsigned int nchildren;
1901 int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
1903 if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
1904 if (!XQueryTree(xDisplay, win, &root, &parent,
1905 &children, &nchildren)) break;
1906 if (children) XFree((void *)children);
1907 if (parent == root || parent == 0) break;
1910 XSetErrorHandler(oldHandler);
1912 if (oldICSInteractionTitle == NULL) {
1913 oldICSInteractionTitle = "xterm";
1916 printf("\033]0;%s\007", message);
1923 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
1925 GtkWidget *w = (GtkWidget *) opt->handle;
1932 strcpy(bgcolor, "black");
1933 strcpy(fgcolor, "white");
1935 strcpy(bgcolor, "white");
1936 strcpy(fgcolor, "black");
1939 appData.lowTimeWarning &&
1940 (timer / 1000) < appData.icsAlarmTime) {
1941 strcpy(fgcolor, appData.lowTimeWarningColor);
1944 gdk_color_parse( bgcolor, &col );
1945 gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
1947 if (appData.clockMode) {
1948 markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>", appData.clockFont,
1949 bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
1950 // markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
1951 // bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
1953 markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s </span>", appData.clockFont,
1954 bgcolor, fgcolor, color);
1955 // markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s </span>",
1956 // bgcolor, fgcolor, color);
1958 gtk_label_set_markup(GTK_LABEL(w), markup);
1962 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
1965 SetClockIcon (int color)
1967 GdkPixbuf *pm = *clockIcons[color];
1968 if (mainwindowIcon != pm) {
1969 mainwindowIcon = pm;
1970 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1974 #define INPUT_SOURCE_BUF_SIZE 8192
1983 char buf[INPUT_SOURCE_BUF_SIZE];
1988 DoInputCallback(io, cond, data)
1993 /* read input from one of the input source (for example a chess program, ICS, etc).
1994 * and call a function that will handle the input
2001 /* All information (callback function, file descriptor, etc) is
2002 * saved in an InputSource structure
2004 InputSource *is = (InputSource *) data;
2006 if (is->lineByLine) {
2007 count = read(is->fd, is->unused,
2008 INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
2010 if(count == 0 && is->kind == CPReal && shells[ChatDlg]) { // [HGM] absence of terminal is no error if ICS Console present
2011 RemoveInputSource(is); // cease reading stdin
2012 stdoutClosed = TRUE; // suppress future output
2015 (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
2018 is->unused += count;
2020 /* break input into lines and call the callback function on each
2023 while (p < is->unused) {
2024 q = memchr(p, '\n', is->unused - p);
2025 if (q == NULL) break;
2027 (is->func)(is, is->closure, p, q - p, 0);
2030 /* remember not yet used part of the buffer */
2032 while (p < is->unused) {
2037 /* read maximum length of input buffer and send the whole buffer
2038 * to the callback function
2040 count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2045 (is->func)(is, is->closure, is->buf, count, error);
2047 return True; // Must return true or the watch will be removed
2050 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2057 GIOChannel *channel;
2058 ChildProc *cp = (ChildProc *) pr;
2060 is = (InputSource *) calloc(1, sizeof(InputSource));
2061 is->lineByLine = lineByLine;
2065 is->fd = fileno(stdin);
2067 is->kind = cp->kind;
2068 is->fd = cp->fdFrom;
2071 is->unused = is->buf;
2075 /* GTK-TODO: will this work on windows?*/
2077 channel = g_io_channel_unix_new(is->fd);
2078 g_io_channel_set_close_on_unref (channel, TRUE);
2079 is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2081 is->closure = closure;
2082 return (InputSourceRef) is;
2087 RemoveInputSource(isr)
2090 InputSource *is = (InputSource *) isr;
2092 if (is->sid == 0) return;
2093 g_source_remove(is->sid);
2100 static Boolean frameWaiting;
2103 FrameAlarm (int sig)
2105 frameWaiting = False;
2106 /* In case System-V style signals. Needed?? */
2107 signal(SIGALRM, FrameAlarm);
2111 FrameDelay (int time)
2113 struct itimerval delay;
2116 frameWaiting = True;
2117 signal(SIGALRM, FrameAlarm);
2118 delay.it_interval.tv_sec =
2119 delay.it_value.tv_sec = time / 1000;
2120 delay.it_interval.tv_usec =
2121 delay.it_value.tv_usec = (time % 1000) * 1000;
2122 setitimer(ITIMER_REAL, &delay, NULL);
2123 while (frameWaiting) pause();
2124 delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2125 delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2126 setitimer(ITIMER_REAL, &delay, NULL);
2133 FrameDelay (int time)
2136 XSync(xDisplay, False);
2138 // gtk_main_iteration_do(False);
2141 usleep(time * 1000);
2147 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2149 char buf[MSG_SIZ], *logoName = buf;
2150 if(appData.logo[n][0]) {
2151 logoName = appData.logo[n];
2152 } else if(appData.autoLogo) {
2153 if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2154 sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2155 } else if(appData.directory[n] && appData.directory[n][0]) {
2156 sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2160 { ASSIGN(cps->programLogo, logoName); }
2164 UpdateLogos (int displ)
2166 if(optList[W_WHITE-1].handle == NULL) return;
2167 LoadLogo(&first, 0, 0);
2168 LoadLogo(&second, 1, appData.icsActive);
2169 if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2173 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2184 GtkFileFilter *gtkfilter;
2185 GtkFileFilter *gtkfilter_all;
2187 char fileext[10] = "";
2188 char *result = NULL;
2191 /* make a copy of the filter string, so that strtok can work with it*/
2192 cp = strdup(filter);
2194 /* add filters for file extensions */
2195 gtkfilter = gtk_file_filter_new();
2196 gtkfilter_all = gtk_file_filter_new();
2198 /* one filter to show everything */
2199 gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2200 gtk_file_filter_set_name (gtkfilter_all, "All Files");
2202 /* add filter if present */
2203 result = strtok(cp, space);
2204 while( result != NULL ) {
2205 snprintf(fileext,10,"*%s",result);
2206 result = strtok( NULL, space );
2207 gtk_file_filter_add_pattern(gtkfilter, fileext);
2210 /* second filter to only show what's useful */
2211 gtk_file_filter_set_name (gtkfilter,filter);
2213 if (openMode[0] == 'r')
2215 dialog = gtk_file_chooser_dialog_new (label,
2217 GTK_FILE_CHOOSER_ACTION_OPEN,
2218 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2219 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2224 dialog = gtk_file_chooser_dialog_new (label,
2226 GTK_FILE_CHOOSER_ACTION_SAVE,
2227 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2228 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2230 /* add filename suggestions */
2231 if (strlen(def) > 0 )
2232 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2234 //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2238 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2239 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2240 /* activate filter */
2241 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2243 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2248 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2251 f = fopen(filename, openMode);
2254 DisplayError(_("Failed to open file"), errno);
2258 /* TODO add indec */
2260 ASSIGN(*name, filename);
2261 ScheduleDelayedEvent(DelayedLoad, 50);
2266 gtk_widget_destroy (dialog);