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);
405 appData.icsFont = strdup(name);
408 appData.tagsFont = strdup(name);
411 appData.commentFont = strdup(name);
413 case MOVEHISTORY_FONT:
414 appData.historyFont = strdup(name);
417 appData.gameListFont = strdup(name);
422 fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
427 { // only 2 fonts currently
428 appData.clockFont = strdup(CLOCK_FONT_NAME);
429 appData.coordFont = strdup(COORD_FONT_NAME);
430 appData.font = strdup(DEFAULT_FONT_NAME);
431 appData.icsFont = strdup(CONSOLE_FONT_NAME);
432 appData.tagsFont = strdup(TAGS_FONT_NAME);
433 appData.commentFont = strdup(COMMENT_FONT_NAME);
434 appData.historyFont = strdup(HISTORY_FONT_NAME);
435 appData.gameListFont = strdup(GAMELIST_FONT_NAME);
440 { // no-op, until we identify the code for this already in XBoard and move it here
444 ParseColor (int n, char *name)
445 { // in XBoard, just copy the color-name string
446 if(colorVariable[n]) *(char**)colorVariable[n] = strdup(name);
452 return *(char**)colorVariable[n];
456 ParseTextAttribs (ColorClass cc, char *s)
458 (&appData.colorShout)[cc] = strdup(s);
462 ParseBoardSize (void *addr, char *name)
464 appData.boardSize = strdup(name);
469 { // In XBoard the sound-playing program takes care of obtaining the actual sound
473 SetCommPortDefaults ()
474 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
477 // [HGM] args: these three cases taken out to stay in front-end
479 SaveFontArg (FILE *f, ArgDescriptor *ad)
482 int i, n = (int)(intptr_t)ad->argLoc;
484 case 0: // CLOCK_FONT
485 name = appData.clockFont;
487 case 1: // MESSAGE_FONT
490 case 2: // COORD_FONT
491 name = appData.coordFont;
494 name = appData.icsFont;
497 name = appData.tagsFont;
500 name = appData.commentFont;
502 case MOVEHISTORY_FONT:
503 name = appData.historyFont;
506 name = appData.gameListFont;
511 for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
512 if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
513 fontTable[n][squareSize] = strdup(name);
514 fontValid[n][squareSize] = True;
517 for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
518 fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
523 { // nothing to do, as the sounds are at all times represented by their text-string names already
527 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
528 { // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
529 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
533 SaveColor (FILE *f, ArgDescriptor *ad)
534 { // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
535 if(colorVariable[(int)(intptr_t)ad->argLoc])
536 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
540 SaveBoardSize (FILE *f, char *name, void *addr)
541 { // wrapper to shield back-end from BoardSize & sizeInfo
542 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
546 ParseCommPortSettings (char *s)
547 { // no such option in XBoard (yet)
553 GetActualPlacement (GtkWidget *shell, WindowPlacement *wp)
557 gtk_widget_get_allocation(shell, &a);
558 gtk_window_get_position(GTK_WINDOW(shell), &a.x, &a.y);
562 wp->height = a.height;
563 //printf("placement: (%d,%d) %dx%d\n", a.x, a.y, a.width, a.height);
564 frameX = 3; frameY = 3; // remember to decide if windows touch
568 GetPlacement (DialogClass dlg, WindowPlacement *wp)
569 { // wrapper to shield back-end from widget type
570 if(shellUp[dlg]) GetActualPlacement(shells[dlg], wp);
575 { // wrapper to shield use of window handles from back-end (make addressible by number?)
576 // In XBoard this will have to wait until awareness of window parameters is implemented
577 GetActualPlacement(shellWidget, &wpMain);
578 if(shellUp[EngOutDlg]) GetActualPlacement(shells[EngOutDlg], &wpEngineOutput);
579 if(shellUp[HistoryDlg]) GetActualPlacement(shells[HistoryDlg], &wpMoveHistory);
580 if(shellUp[EvalGraphDlg]) GetActualPlacement(shells[EvalGraphDlg], &wpEvalGraph);
581 if(shellUp[GameListDlg]) GetActualPlacement(shells[GameListDlg], &wpGameList);
582 if(shellUp[CommentDlg]) GetActualPlacement(shells[CommentDlg], &wpComment);
583 if(shellUp[TagsDlg]) GetActualPlacement(shells[TagsDlg], &wpTags);
584 GetPlacement(ChatDlg, &wpConsole); if(appData.icsActive) wpConsole.visible = shellUp[ChatDlg];
588 PrintCommPortSettings (FILE *f, char *name)
589 { // This option does not exist in XBoard
593 EnsureOnScreen (int *x, int *y, int minX, int minY)
600 { // [HGM] args: allows testing if main window is realized from back-end
601 return DialogExists(BoardWindow);
605 PopUpStartupDialog ()
606 { // start menu not implemented in XBoard
610 ConvertToLine (int argc, char **argv)
612 static char line[128*1024], buf[1024];
616 for(i=1; i<argc; i++)
618 if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
619 && argv[i][0] != '{' )
620 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
622 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
623 strncat(line, buf, 128*1024 - strlen(line) - 1 );
626 line[strlen(line)-1] = NULLCHAR;
630 //--------------------------------------------------------------------------------------------
635 ResizeBoardWindow (int w, int h, int inhibit)
638 // if(clockKludge) return; // ignore as long as clock does not have final height
639 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
640 w += marginW + 1; // [HGM] not sure why the +1 is (sometimes) needed...
641 h += marginH + a.height + 1;
642 gtk_window_resize(GTK_WINDOW(shellWidget), w, h);
647 { // dummy, as the GTK code does not make colors in advance
652 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
653 { // determine what fonts to use, and create them
655 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
656 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
657 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
658 appData.font = fontTable[MESSAGE_FONT][squareSize];
659 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
660 appData.coordFont = fontTable[COORD_FONT][squareSize];
661 if(!fontIsSet[CONSOLE_FONT] && fontValid[CONSOLE_FONT][squareSize])
662 appData.icsFont = fontTable[CONSOLE_FONT][squareSize];
663 if(!fontIsSet[EDITTAGS_FONT] && fontValid[EDITTAGS_FONT][squareSize])
664 appData.tagsFont = fontTable[EDITTAGS_FONT][squareSize];
665 if(!fontIsSet[COMMENT_FONT] && fontValid[COMMENT_FONT][squareSize])
666 appData.commentFont = fontTable[COMMENT_FONT][squareSize];
667 if(!fontIsSet[MOVEHISTORY_FONT] && fontValid[MOVEHISTORY_FONT][squareSize])
668 appData.historyFont = fontTable[MOVEHISTORY_FONT][squareSize];
669 if(!fontIsSet[GAMELIST_FONT] && fontValid[GAMELIST_FONT][squareSize])
670 appData.gameListFont = fontTable[GAMELIST_FONT][squareSize];
672 appData.font = InsertPxlSize(appData.font, coordFontPxlSize);
673 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
674 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
675 appData.icsFont = InsertPxlSize(appData.icsFont, coordFontPxlSize);
676 appData.tagsFont = InsertPxlSize(appData.tagsFont, coordFontPxlSize);
677 appData.commentFont = InsertPxlSize(appData.commentFont, coordFontPxlSize);
678 appData.historyFont = InsertPxlSize(appData.historyFont, coordFontPxlSize);
679 appData.gameListFont = InsertPxlSize(appData.gameListFont, coordFontPxlSize);
685 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
686 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
687 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
688 appData.font = fontTable[MESSAGE_FONT][squareSize];
689 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
690 appData.coordFont = fontTable[COORD_FONT][squareSize];
693 appData.font = InsertPxlSize(appData.font, fontPxlSize);
694 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
695 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
696 fontSet = CreateFontSet(appData.font);
697 clockFontSet = CreateFontSet(appData.clockFont);
699 /* For the coordFont, use the 0th font of the fontset. */
700 XFontSet coordFontSet = CreateFontSet(appData.coordFont);
701 XFontStruct **font_struct_list;
702 XFontSetExtents *fontSize;
703 char **font_name_list;
704 XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
705 coordFontID = XLoadFont(xDisplay, font_name_list[0]);
706 coordFontStruct = XQueryFont(xDisplay, coordFontID);
707 fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
708 textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
711 appData.font = FindFont(appData.font, fontPxlSize);
712 appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
713 appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
714 clockFontID = XLoadFont(xDisplay, appData.clockFont);
715 clockFontStruct = XQueryFont(xDisplay, clockFontID);
716 coordFontID = XLoadFont(xDisplay, appData.coordFont);
717 coordFontStruct = XQueryFont(xDisplay, coordFontID);
718 // textHeight in !NLS mode!
720 countFontID = coordFontID; // [HGM] holdings
721 countFontStruct = coordFontStruct;
723 xdb = XtDatabase(xDisplay);
725 XrmPutLineResource(&xdb, "*international: True");
726 vTo.size = sizeof(XFontSet);
727 vTo.addr = (XtPointer) &fontSet;
728 XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
730 XrmPutStringResource(&xdb, "*font", appData.font);
741 case ArgInt: p = " N"; break;
742 case ArgString: p = " STR"; break;
743 case ArgBoolean: p = " TF"; break;
744 case ArgSettingsFilename:
745 case ArgBackupSettingsFile:
746 case ArgFilename: p = " FILE"; break;
747 case ArgX: p = " Nx"; break;
748 case ArgY: p = " Ny"; break;
749 case ArgAttribs: p = " TEXTCOL"; break;
750 case ArgColor: p = " COL"; break;
751 case ArgFont: p = " FONT"; break;
752 case ArgBoardSize: p = " SIZE"; break;
753 case ArgFloat: p = " FLOAT"; break;
758 case ArgCommSettings:
770 ArgDescriptor *q, *p = argDescriptors+5;
771 printf("\nXBoard accepts the following options:\n"
772 "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
773 " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
774 " SIZE = board-size spec(s)\n"
775 " Within parentheses are short forms, or options to set to true or false.\n"
776 " Persistent options (saved in the settings file) are marked with *)\n\n");
778 if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
779 snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
780 if(p->save) strcat(buf+len, "*");
781 for(q=p+1; q->argLoc == p->argLoc; q++) {
782 if(q->argName[0] == '-') continue;
783 strcat(buf+len, q == p+1 ? " (" : " ");
784 sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
786 if(q != p+1) strcat(buf+len, ")");
788 if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
791 if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
795 SlaveResize (Option *opt)
797 static int slaveW, slaveH, w, h;
800 gtk_widget_get_allocation(shells[DummyDlg], &a);
801 w = a.width; h = a.height;
802 gtk_widget_get_allocation(opt->handle, &a);
803 slaveW = w - opt->max; // [HGM] needed to set new shellWidget size when we resize board
804 slaveH = h - a.height + 13;
806 gtk_window_resize(GTK_WINDOW(shells[DummyDlg]), slaveW + opt->max, slaveH + opt->value);
810 static char clickedFile[MSG_SIZ];
814 StartNewXBoard(GtkosxApplication *app, gchar *path, gpointer user_data)
815 { // handler of OSX OpenFile signal, which sends us the filename of clicked file or first argument
816 if(suppress) { // we just started XBoard without arguments
817 strncpy(clickedFile, path, MSG_SIZ); // remember file name, but otherwise ignore
818 } else { // we are running something presumably useful
820 snprintf(buf, MSG_SIZ, "open -n -a \"xboard\" --args \"%s\"", path);
821 system(buf); // start new instance on this file
826 GtkosxApplication *theApp;
830 main (int argc, char **argv)
832 int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
833 int boardWidth, w, h; //, boardHeight;
835 int forceMono = False;
837 srandom(time(0)); // [HGM] book: make random truly random
839 setbuf(stdout, NULL);
840 setbuf(stderr, NULL);
843 if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
844 printf("%s version %s\n\n configure options: %s\n", PACKAGE_NAME, PACKAGE_VERSION, CONFIGURE_OPTIONS);
848 if(argc > 1 && !strcmp(argv[1], "--help" )) {
854 gtk_init (&argc, &argv);
856 { // prepare to catch OX OpenFile signal, which will tell us the clicked file
857 char *path = gtkosx_application_get_bundle_path();
858 theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
859 strncpy(dataDir, path, MSG_SIZ);
860 snprintf(masterSettings, MSG_SIZ, "%s/Contents/Resources/etc/xboard.conf", path);
861 suppress = (argc == 1 || argc > 1 && argv[1][00] != '-'); // OSX sends signal even if name was already argv[1]!
862 g_signal_connect(theApp, "NSApplicationOpenFile", G_CALLBACK(StartNewXBoard), NULL);
863 // we must call application ready before we can get the signal,
864 // and supply a (dummy) menu bar before that, to avoid problems with dual apples in it
865 gtkosx_application_set_menu_bar(theApp, GTK_MENU_SHELL(gtk_menu_bar_new()));
866 gtkosx_application_ready(theApp);
867 if(argc == 1) { // called without args: OSX open-file signal might follow
868 static char *fakeArgv[3] = {NULL, clickedFile, NULL};
869 usleep(10000); // wait 10 msec (and hope this is long enough).
870 while(gtk_events_pending())
871 gtk_main_iteration(); // process all events that came in upto now
872 suppress = 0; // future open-file signals should start new instance
873 if(clickedFile[0]) { // we were sent an open-file signal with filename!
874 fakeArgv[0] = argv[0];
875 argc = 2; argv = fakeArgv; // fake that we were called as "xboard filename"
881 if(argc > 1 && !strcmp(argv[1], "--show-config")) { // [HGM] install: called to print config info
882 typedef struct {char *name, *value; } Config;
883 static Config configList[] = {
884 { "Datadir", DATADIR },
885 { "Sysconfdir", SYSCONFDIR },
890 for(i=0; configList[i].name; i++) {
891 if(argc > 2 && strcmp(argv[2], configList[i].name)) continue;
892 if(argc > 2) printf("%s", configList[i].value);
893 else printf("%-12s: %s\n", configList[i].name, configList[i].value);
898 /* set up keyboard accelerators group */
899 GtkAccelerators = gtk_accel_group_new();
901 programName = strrchr(argv[0], '/');
902 if (programName == NULL)
903 programName = argv[0];
908 // if (appData.debugMode) {
909 // fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
912 bindtextdomain(PACKAGE, LOCALEDIR);
913 bind_textdomain_codeset(PACKAGE, "UTF-8"); // needed when creating markup for the clocks
917 appData.boardSize = "";
918 InitAppData(ConvertToLine(argc, argv));
920 if (p == NULL) p = "/tmp";
921 i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
922 gameCopyFilename = (char*) malloc(i);
923 gamePasteFilename = (char*) malloc(i);
924 snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
925 snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
927 { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
928 static char buf[MSG_SIZ];
929 snprintf(buf, MSG_SIZ, appData.sysOpen, DATADIR);
930 ASSIGN(appData.sysOpen, buf); // expand %s in -openCommand to DATADIR (usefull for OS X configuring)
931 EscapeExpand(buf, appData.firstInitString);
932 appData.firstInitString = strdup(buf);
933 EscapeExpand(buf, appData.secondInitString);
934 appData.secondInitString = strdup(buf);
935 EscapeExpand(buf, appData.firstComputerString);
936 appData.firstComputerString = strdup(buf);
937 EscapeExpand(buf, appData.secondComputerString);
938 appData.secondComputerString = strdup(buf);
941 if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
944 if (chdir(chessDir) != 0) {
945 fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
951 if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
952 /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
953 if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL) {
954 printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
957 setbuf(debugFP, NULL);
961 if (appData.debugMode) {
962 fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
966 /* [HGM,HR] make sure board size is acceptable */
967 if(appData.NrFiles > BOARD_FILES ||
968 appData.NrRanks > BOARD_RANKS )
969 DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
972 /* This feature does not work; animation needs a rewrite */
973 appData.highlightDragging = FALSE;
977 gameInfo.variant = StringToVariant(appData.variant);
981 * determine size, based on supplied or remembered -size, or screen size
983 if (isdigit(appData.boardSize[0])) {
984 i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
985 &lineGap, &clockFontPxlSize, &coordFontPxlSize,
986 &fontPxlSize, &smallLayout, &tinyLayout);
988 fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
989 programName, appData.boardSize);
993 squareSize = (squareSize*8 + BOARD_WIDTH/2)/BOARD_WIDTH; // scale height
995 /* Find some defaults; use the nearest known size */
996 SizeDefaults *szd, *nearest;
997 int distance = 99999;
998 nearest = szd = sizeDefaults;
999 while (szd->name != NULL) {
1000 if (abs(szd->squareSize - squareSize) < distance) {
1002 distance = abs(szd->squareSize - squareSize);
1003 if (distance == 0) break;
1007 if (i < 2) lineGap = nearest->lineGap;
1008 if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
1009 if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
1010 if (i < 5) fontPxlSize = nearest->fontPxlSize;
1011 if (i < 6) smallLayout = nearest->smallLayout;
1012 if (i < 7) tinyLayout = nearest->tinyLayout;
1015 SizeDefaults *szd = sizeDefaults;
1016 if (*appData.boardSize == NULLCHAR) {
1017 GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow)); // TODO: this does not work, as no mainwindow yet
1018 guint screenwidth = gdk_screen_get_width(screen);
1019 guint screenheight = gdk_screen_get_height(screen);
1020 while (screenwidth < (szd->minScreenSize*BOARD_WIDTH + 4)/8 ||
1021 screenheight < (szd->minScreenSize*BOARD_HEIGHT + 4)/8) {
1024 if (szd->name == NULL) szd--;
1025 appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
1027 while (szd->name != NULL &&
1028 StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
1029 if (szd->name == NULL) {
1030 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
1031 programName, appData.boardSize);
1035 squareSize = szd->squareSize;
1036 lineGap = szd->lineGap;
1037 clockFontPxlSize = szd->clockFontPxlSize;
1038 coordFontPxlSize = szd->coordFontPxlSize;
1039 fontPxlSize = szd->fontPxlSize;
1040 smallLayout = szd->smallLayout;
1041 tinyLayout = szd->tinyLayout;
1042 // [HGM] font: use defaults from settings file if available and not overruled
1045 defaultLineGap = lineGap;
1046 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
1048 /* [HR] height treated separately (hacked) */
1049 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
1050 // boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1053 * Determine what fonts to use.
1055 InitializeFonts((2*clockFontPxlSize+1)/3, coordFontPxlSize, fontPxlSize);
1058 * Detect if there are not enough colors available and adapt.
1061 if (DefaultDepth(xDisplay, xScreen) <= 2) {
1062 appData.monoMode = True;
1066 forceMono = MakeColors();
1069 fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1071 appData.monoMode = True;
1074 ParseIcsTextColors();
1080 layoutName = "tinyLayout";
1081 } else if (smallLayout) {
1082 layoutName = "smallLayout";
1084 layoutName = "normalLayout";
1087 wpMain.width = -1; // prevent popup sizes window
1088 optList = BoardPopUp(squareSize, lineGap, (void*)
1098 InitDrawingHandle(optList + W_BOARD);
1099 shellWidget = shells[BoardWindow];
1100 currBoard = &optList[W_BOARD];
1101 boardWidget = optList[W_BOARD].handle;
1102 menuBarWidget = optList[W_MENU].handle;
1103 dropMenu = optList[W_DROP].handle;
1104 titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1106 formWidget = XtParent(boardWidget);
1107 XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1108 XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1109 XtGetValues(optList[W_WHITE].handle, args, 2);
1110 if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1111 XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1112 XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1113 XtGetValues(optList[W_PAUSE].handle, args, 2);
1117 // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1118 // not need to go into InitDrawingSizes().
1122 // add accelerators to main shell
1123 gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1126 * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1128 WhiteIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_white.svg", NULL);
1129 BlackIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_black.svg", NULL);
1130 SetClockIcon(0); // sets white icon
1134 * Create a cursor for the board widget.
1137 window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1138 XChangeWindowAttributes(xDisplay, xBoardWindow,
1139 CWCursor, &window_attributes);
1143 * Inhibit shell resizing.
1146 shellArgs[0].value = (XtArgVal) &w;
1147 shellArgs[1].value = (XtArgVal) &h;
1148 XtGetValues(shellWidget, shellArgs, 2);
1149 shellArgs[4].value = shellArgs[2].value = w;
1150 shellArgs[5].value = shellArgs[3].value = h;
1151 // XtSetValues(shellWidget, &shellArgs[2], 4);
1154 // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1155 // It wil only become known asynchronously, when we first write a string into it.
1156 // This will then change the clock widget height, which triggers resizing the top-level window
1157 // and a configure event. Only then can we know the total height of the top-level window,
1158 // and calculate the height we need. The clockKludge flag suppresses all resizing until
1159 // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1162 gtk_widget_get_allocation(shells[BoardWindow], &a);
1163 w = a.width; h = a.height;
1164 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1165 clockKludge = hc = a.height;
1166 gtk_widget_get_allocation(boardWidget, &a);
1167 marginW = w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1168 marginH = h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1174 if(appData.logoSize)
1175 { // locate and read user logo
1177 snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1178 ASSIGN(userLogo, buf);
1181 if (appData.animate || appData.animateDragging)
1184 g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1185 g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1187 /* [AS] Restore layout */
1188 if( wpMoveHistory.visible ) {
1192 if( wpEvalGraph.visible )
1197 if( wpEngineOutput.visible ) {
1198 EngineOutputPopUp();
1201 if( wpConsole.visible && appData.icsActive ) {
1206 gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1211 if (errorExitStatus == -1) {
1212 if (appData.icsActive) {
1213 /* We now wait until we see "login:" from the ICS before
1214 sending the logon script (problems with timestamp otherwise) */
1215 /*ICSInitScript();*/
1216 if (appData.icsInputBox) ICSInputBoxPopUp();
1220 signal(SIGWINCH, TermSizeSigHandler);
1222 signal(SIGINT, IntSigHandler);
1223 signal(SIGTERM, IntSigHandler);
1224 if (*appData.cmailGameName != NULLCHAR) {
1225 signal(SIGUSR1, CmailSigHandler);
1230 // XtSetKeyboardFocus(shellWidget, formWidget);
1232 XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1235 /* check for GTK events and process them */
1238 gtk_main_iteration();
1241 if (appData.debugMode) fclose(debugFP); // [DM] debug
1248 while(gtk_events_pending()) gtk_main_iteration();
1252 TermSizeSigHandler (int sig)
1258 IntSigHandler (int sig)
1264 CmailSigHandler (int sig)
1269 signal(SIGUSR1, SIG_IGN); /* suspend handler */
1271 /* Activate call-back function CmailSigHandlerCallBack() */
1272 OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1274 signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1278 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1281 ReloadCmailMsgEvent(TRUE); /* Reload cmail msg */
1283 /**** end signal code ****/
1286 #define Abs(n) ((n)<0 ? -(n) : (n))
1289 InsertPxlSize (char *pattern, int targetPxlSize)
1292 snprintf(buf, MSG_SIZ, pattern, targetPxlSize); // pattern is something like "Sans Bold %d"
1299 InsertPxlSize (char *pattern, int targetPxlSize)
1301 char *base_fnt_lst, strInt[12], *p, *q;
1302 int alternatives, i, len, strIntLen;
1305 * Replace the "*" (if present) in the pixel-size slot of each
1306 * alternative with the targetPxlSize.
1310 while ((p = strchr(p, ',')) != NULL) {
1314 snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1315 strIntLen = strlen(strInt);
1316 base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1320 while (alternatives--) {
1321 char *comma = strchr(p, ',');
1322 for (i=0; i<14; i++) {
1323 char *hyphen = strchr(p, '-');
1325 if (comma && hyphen > comma) break;
1326 len = hyphen + 1 - p;
1327 if (i == 7 && *p == '*' && len == 2) {
1329 memcpy(q, strInt, strIntLen);
1339 len = comma + 1 - p;
1346 return base_fnt_lst;
1352 CreateFontSet (char *base_fnt_lst)
1355 char **missing_list;
1359 fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1360 &missing_list, &missing_count, &def_string);
1361 if (appData.debugMode) {
1363 XFontStruct **font_struct_list;
1364 char **font_name_list;
1365 fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1367 fprintf(debugFP, " got list %s, locale %s\n",
1368 XBaseFontNameListOfFontSet(fntSet),
1369 XLocaleOfFontSet(fntSet));
1370 count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1371 for (i = 0; i < count; i++) {
1372 fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1375 for (i = 0; i < missing_count; i++) {
1376 fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1379 if (fntSet == NULL) {
1380 fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1386 #else // not ENABLE_NLS
1388 * Find a font that matches "pattern" that is as close as
1389 * possible to the targetPxlSize. Prefer fonts that are k
1390 * pixels smaller to fonts that are k pixels larger. The
1391 * pattern must be in the X Consortium standard format,
1392 * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1393 * The return value should be freed with XtFree when no
1398 FindFont (char *pattern, int targetPxlSize)
1400 char **fonts, *p, *best, *scalable, *scalableTail;
1401 int i, j, nfonts, minerr, err, pxlSize;
1403 fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1405 fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1406 programName, pattern);
1413 for (i=0; i<nfonts; i++) {
1416 if (*p != '-') continue;
1418 if (*p == NULLCHAR) break;
1419 if (*p++ == '-') j++;
1421 if (j < 7) continue;
1424 scalable = fonts[i];
1427 err = pxlSize - targetPxlSize;
1428 if (Abs(err) < Abs(minerr) ||
1429 (minerr > 0 && err < 0 && -err == minerr)) {
1435 if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1436 /* If the error is too big and there is a scalable font,
1437 use the scalable font. */
1438 int headlen = scalableTail - scalable;
1439 p = (char *) XtMalloc(strlen(scalable) + 10);
1440 while (isdigit(*scalableTail)) scalableTail++;
1441 sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1443 p = (char *) XtMalloc(strlen(best) + 2);
1444 safeStrCpy(p, best, strlen(best)+1 );
1446 if (appData.debugMode) {
1447 fprintf(debugFP, "resolved %s at pixel size %d\n to %s\n",
1448 pattern, targetPxlSize, p);
1450 XFreeFontNames(fonts);
1457 EnableNamedMenuItem (char *menuRef, int state)
1459 MenuItem *item = MenuNameToItem(menuRef);
1461 if(item && item->handle) gtk_widget_set_sensitive(item->handle, state);
1465 EnableButtonBar (int state)
1468 XtSetSensitive(optList[W_BUTTON].handle, state);
1474 SetMenuEnables (Enables *enab)
1476 while (enab->name != NULL) {
1477 EnableNamedMenuItem(enab->name, enab->value);
1482 gboolean KeyPressProc(window, eventkey, data)
1484 GdkEventKey *eventkey;
1488 MoveTypeInProc(eventkey); // pop up for typed in moves
1491 /* check for other key values */
1492 switch(eventkey->keyval) {
1504 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1505 { // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1507 if(*nprms == 0) return;
1508 item = MenuNameToItem(prms[0]);
1509 if(item) ((MenuProc *) item->proc) ();
1523 for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1524 entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1525 p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1526 dmEnables[i].piece);
1527 XtSetSensitive(entry, p != NULL || !appData.testLegality
1528 /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1529 && !appData.icsActive));
1531 while (p && *p++ == dmEnables[i].piece) count++;
1532 snprintf(label, sizeof(label), "%s %d", dmEnables[i].widget, count);
1534 XtSetArg(args[j], XtNlabel, label); j++;
1535 XtSetValues(entry, args, j);
1541 do_flash_delay (unsigned long msec)
1547 FlashDelay (int flash_delay)
1549 if(flash_delay) do_flash_delay(flash_delay);
1553 Fraction (int x, int start, int stop)
1555 double f = ((double) x - start)/(stop - start);
1556 if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1560 static WindowPlacement wpNew;
1563 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1565 int touch=0, fudge = 2, f = 2;
1566 GetActualPlacement(sh, wp);
1567 if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x) < fudge) touch = 1; else // right touch
1568 if(abs(wp->x + wp->width + 2*frameX + f - wpMain.x) < fudge) touch = 2; else // left touch
1569 if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1570 if(abs(wp->y + wp->height + frameX + frameY + f - wpMain.y) < fudge) touch = 4; // top touch
1571 //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);
1572 if(!touch ) return; // only windows that touch co-move
1573 if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1574 int heightInc = wpNew.height - wpMain.height;
1575 double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1576 double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1577 wp->y += fracTop * heightInc;
1578 heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1580 if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1582 wp->height += heightInc;
1583 } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1584 int widthInc = wpNew.width - wpMain.width;
1585 double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1586 double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1587 wp->y += fracLeft * widthInc;
1588 widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1590 if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1592 wp->width += widthInc;
1594 wp->x += wpNew.x - wpMain.x;
1595 wp->y += wpNew.y - wpMain.y;
1596 if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1597 if(touch == 3) wp->y += wpNew.height - wpMain.height;
1599 XtSetArg(args[j], XtNx, wp->x); j++;
1600 XtSetArg(args[j], XtNy, wp->y); j++;
1601 XtSetValues(sh, args, j);
1603 gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1604 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1605 gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1609 ReSize (WindowPlacement *wp)
1612 int sqx, sqy, w, h, hc, lg = lineGap;
1613 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1614 hc = a.height; // clock height can depend on single / double line clock text!
1615 if(clockKludge && hc != clockKludge) wp->height += hc - clockKludge, clockKludge = 0;
1616 wpMain.height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1617 if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1618 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1619 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1620 if(sqy < sqx) sqx = sqy;
1621 if(sqx < 20) return;
1622 if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1623 lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1624 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1625 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1626 if(sqy < sqx) sqx = sqy;
1628 if(sqx != squareSize) {
1629 squareSize = sqx; // adopt new square size
1630 CreatePNGPieces(); // make newly scaled pieces
1631 InitDrawingSizes(0, 0); // creates grid etc.
1632 } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1633 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1634 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1635 if(optList[W_BOARD].max > w) optList[W_BOARD].max = w;
1636 if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1639 static guint delayedDragTag = 0;
1648 // GetActualPlacement(shellWidget, &wpNew);
1649 if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1650 wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1651 busy = 0; return; // false alarm
1654 if(appData.useStickyWindows) {
1655 if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1656 if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1657 if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1658 if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1659 if(shellUp[ChatDlg]) CoDrag(shells[ChatDlg], &wpConsole);
1662 DrawPosition(True, NULL);
1663 if(delayedDragTag) g_source_remove(delayedDragTag);
1664 delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1671 //printf("old timr = %d\n", delayedDragTag);
1672 if(delayedDragTag) g_source_remove(delayedDragTag);
1673 delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1674 //printf("new timr = %d\n", delayedDragTag);
1678 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1680 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1682 wpNew.x = event->configure.x;
1683 wpNew.y = event->configure.y;
1684 wpNew.width = event->configure.width;
1685 wpNew.height = event->configure.height;
1686 DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1692 /* Disable all user input other than deleting the window */
1693 static int frozen = 0;
1699 /* Grab by a widget that doesn't accept input */
1700 gtk_grab_add(optList[W_MESSG].handle);
1704 /* Undo a FreezeUI */
1708 if (!frozen) return;
1709 gtk_grab_remove(optList[W_MESSG].handle);
1716 static int oldPausing = FALSE;
1717 static GameMode oldmode = (GameMode) -1;
1719 if (!boardWidget) return;
1721 if (pausing != oldPausing) {
1722 oldPausing = pausing;
1723 MarkMenuItem("Mode.Pause", pausing);
1725 if (appData.showButtonBar) {
1726 /* Always toggle, don't set. Previous code messes up when
1727 invoked while the button is pressed, as releasing it
1728 toggles the state again. */
1730 gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1731 gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1735 wname = ModeToWidgetName(oldmode);
1736 if (wname != NULL) {
1737 MarkMenuItem(wname, False);
1739 wname = ModeToWidgetName(gameMode);
1740 if (wname != NULL) {
1741 MarkMenuItem(wname, True);
1744 MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1746 /* Maybe all the enables should be handled here, not just this one */
1747 EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1749 DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1754 * Button/menu procedures
1757 void CopyFileToClipboard(gchar *filename)
1759 gchar *selection_tmp;
1763 FILE* f = fopen(filename, "r");
1766 if (f == NULL) return;
1770 selection_tmp = g_try_malloc(len + 1);
1771 if (selection_tmp == NULL) {
1772 printf("Malloc failed in CopyFileToClipboard\n");
1775 count = fread(selection_tmp, 1, len, f);
1778 g_free(selection_tmp);
1781 selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1783 // copy selection_tmp to clipboard
1784 GdkDisplay *gdisp = gdk_display_get_default();
1786 g_free(selection_tmp);
1789 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1790 gtk_clipboard_set_text(cb, selection_tmp, -1);
1791 g_free(selection_tmp);
1795 CopySomething (char *src)
1797 GdkDisplay *gdisp = gdk_display_get_default();
1799 if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1800 if (gdisp == NULL) return;
1801 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1802 gtk_clipboard_set_text(cb, src, -1);
1806 PastePositionProc ()
1808 GdkDisplay *gdisp = gdk_display_get_default();
1812 if (gdisp == NULL) return;
1813 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1814 fenstr = gtk_clipboard_wait_for_text(cb);
1815 if (fenstr==NULL) return; // nothing had been selected to copy
1816 EditPositionPasteFEN(fenstr);
1828 // get game from clipboard
1829 GdkDisplay *gdisp = gdk_display_get_default();
1830 if (gdisp == NULL) return;
1831 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1832 text = gtk_clipboard_wait_for_text(cb);
1833 if (text == NULL) return; // nothing to paste
1836 // write to temp file
1837 if (text == NULL || len == 0) {
1838 return; //nothing to paste
1840 f = fopen(gamePasteFilename, "w");
1842 DisplayError(_("Can't open temp file"), errno);
1845 fwrite(text, 1, len, f);
1849 LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1856 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1862 void MoveTypeInProc(eventkey)
1863 GdkEventKey *eventkey;
1867 // ingnore if ctrl, alt, or meta is pressed
1868 if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1872 buf[0]=eventkey->keyval;
1874 if (eventkey->keyval > 32 && eventkey->keyval < 256)
1875 ConsoleAutoPopUp (buf);
1880 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1882 if (!TempBackwardActive) {
1883 TempBackwardActive = True;
1889 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1891 /* Check to see if triggered by a key release event for a repeating key.
1892 * If so the next queued event will be a key press of the same key at the same time */
1893 if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1895 XPeekEvent(xDisplay, &next);
1896 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1897 next.xkey.keycode == event->xkey.keycode)
1901 TempBackwardActive = False;
1907 { // called from menu
1910 snprintf(buf, MSG_SIZ, "%s ./man.command", appData.sysOpen);
1913 system("xterm -e man xboard &");
1918 SetWindowTitle (char *text, char *title, char *icon)
1923 if (appData.titleInWindow) {
1925 XtSetArg(args[i], XtNlabel, text); i++;
1926 XtSetValues(titleWidget, args, i);
1929 XtSetArg(args[i], XtNiconName, (XtArgVal) icon); i++;
1930 XtSetArg(args[i], XtNtitle, (XtArgVal) title); i++;
1931 XtSetValues(shellWidget, args, i);
1932 XSync(xDisplay, False);
1934 if (appData.titleInWindow) {
1935 SetWidgetLabel(titleWidget, text);
1937 gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1942 DisplayIcsInteractionTitle (String message)
1945 if (oldICSInteractionTitle == NULL) {
1946 /* Magic to find the old window title, adapted from vim */
1947 char *wina = getenv("WINDOWID");
1949 Window win = (Window) atoi(wina);
1950 Window root, parent, *children;
1951 unsigned int nchildren;
1952 int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
1954 if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
1955 if (!XQueryTree(xDisplay, win, &root, &parent,
1956 &children, &nchildren)) break;
1957 if (children) XFree((void *)children);
1958 if (parent == root || parent == 0) break;
1961 XSetErrorHandler(oldHandler);
1963 if (oldICSInteractionTitle == NULL) {
1964 oldICSInteractionTitle = "xterm";
1967 printf("\033]0;%s\007", message);
1974 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
1976 GtkWidget *w = (GtkWidget *) opt->handle;
1983 strcpy(bgcolor, "black");
1984 strcpy(fgcolor, "white");
1986 strcpy(bgcolor, "white");
1987 strcpy(fgcolor, "black");
1990 appData.lowTimeWarning &&
1991 (timer / 1000) < appData.icsAlarmTime) {
1992 strcpy(fgcolor, appData.lowTimeWarningColor);
1995 gdk_color_parse( bgcolor, &col );
1996 gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
1998 if (appData.clockMode) {
1999 markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>", appData.clockFont,
2000 bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2001 // markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
2002 // bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2004 markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s </span>", appData.clockFont,
2005 bgcolor, fgcolor, color);
2006 // markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s </span>",
2007 // bgcolor, fgcolor, color);
2009 gtk_label_set_markup(GTK_LABEL(w), markup);
2013 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
2016 SetClockIcon (int color)
2018 GdkPixbuf *pm = *clockIcons[color];
2019 if (mainwindowIcon != pm) {
2020 mainwindowIcon = pm;
2021 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
2025 #define INPUT_SOURCE_BUF_SIZE 8192
2034 char buf[INPUT_SOURCE_BUF_SIZE];
2039 DoInputCallback(io, cond, data)
2044 /* read input from one of the input source (for example a chess program, ICS, etc).
2045 * and call a function that will handle the input
2052 /* All information (callback function, file descriptor, etc) is
2053 * saved in an InputSource structure
2055 InputSource *is = (InputSource *) data;
2057 if (is->lineByLine) {
2058 count = read(is->fd, is->unused,
2059 INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
2061 if(count == 0 && is->kind == CPReal && shells[ChatDlg]) { // [HGM] absence of terminal is no error if ICS Console present
2062 RemoveInputSource(is); // cease reading stdin
2063 stdoutClosed = TRUE; // suppress future output
2066 (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
2069 is->unused += count;
2071 /* break input into lines and call the callback function on each
2074 while (p < is->unused) {
2075 q = memchr(p, '\n', is->unused - p);
2076 if (q == NULL) break;
2078 (is->func)(is, is->closure, p, q - p, 0);
2081 /* remember not yet used part of the buffer */
2083 while (p < is->unused) {
2088 /* read maximum length of input buffer and send the whole buffer
2089 * to the callback function
2091 count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2096 (is->func)(is, is->closure, is->buf, count, error);
2098 return True; // Must return true or the watch will be removed
2101 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2108 GIOChannel *channel;
2109 ChildProc *cp = (ChildProc *) pr;
2111 is = (InputSource *) calloc(1, sizeof(InputSource));
2112 is->lineByLine = lineByLine;
2116 is->fd = fileno(stdin);
2118 is->kind = cp->kind;
2119 is->fd = cp->fdFrom;
2122 is->unused = is->buf;
2126 /* GTK-TODO: will this work on windows?*/
2128 channel = g_io_channel_unix_new(is->fd);
2129 g_io_channel_set_close_on_unref (channel, TRUE);
2130 is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2132 is->closure = closure;
2133 return (InputSourceRef) is;
2138 RemoveInputSource(isr)
2141 InputSource *is = (InputSource *) isr;
2143 if (is->sid == 0) return;
2144 g_source_remove(is->sid);
2151 static Boolean frameWaiting;
2154 FrameAlarm (int sig)
2156 frameWaiting = False;
2157 /* In case System-V style signals. Needed?? */
2158 signal(SIGALRM, FrameAlarm);
2162 FrameDelay (int time)
2164 struct itimerval delay;
2167 frameWaiting = True;
2168 signal(SIGALRM, FrameAlarm);
2169 delay.it_interval.tv_sec =
2170 delay.it_value.tv_sec = time / 1000;
2171 delay.it_interval.tv_usec =
2172 delay.it_value.tv_usec = (time % 1000) * 1000;
2173 setitimer(ITIMER_REAL, &delay, NULL);
2174 while (frameWaiting) pause();
2175 delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2176 delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2177 setitimer(ITIMER_REAL, &delay, NULL);
2184 FrameDelay (int time)
2187 XSync(xDisplay, False);
2189 // gtk_main_iteration_do(False);
2192 usleep(time * 1000);
2198 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2200 char buf[MSG_SIZ], *logoName = buf;
2201 if(appData.logo[n][0]) {
2202 logoName = appData.logo[n];
2203 } else if(appData.autoLogo) {
2204 if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2205 sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2206 } else if(appData.directory[n] && appData.directory[n][0]) {
2207 sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2211 { ASSIGN(cps->programLogo, logoName); }
2215 UpdateLogos (int displ)
2217 if(optList[W_WHITE-1].handle == NULL) return;
2218 LoadLogo(&first, 0, 0);
2219 LoadLogo(&second, 1, appData.icsActive);
2220 if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2224 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2235 GtkFileFilter *gtkfilter;
2236 GtkFileFilter *gtkfilter_all;
2238 char fileext[10] = "";
2239 char *result = NULL;
2242 /* make a copy of the filter string, so that strtok can work with it*/
2243 cp = strdup(filter);
2245 /* add filters for file extensions */
2246 gtkfilter = gtk_file_filter_new();
2247 gtkfilter_all = gtk_file_filter_new();
2249 /* one filter to show everything */
2250 gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2251 gtk_file_filter_set_name (gtkfilter_all, "All Files");
2253 /* add filter if present */
2254 result = strtok(cp, space);
2255 while( result != NULL ) {
2256 snprintf(fileext,10,"*%s",result);
2257 result = strtok( NULL, space );
2258 gtk_file_filter_add_pattern(gtkfilter, fileext);
2261 /* second filter to only show what's useful */
2262 gtk_file_filter_set_name (gtkfilter,filter);
2264 if (openMode[0] == 'r')
2266 dialog = gtk_file_chooser_dialog_new (label,
2268 GTK_FILE_CHOOSER_ACTION_OPEN,
2269 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2270 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2275 dialog = gtk_file_chooser_dialog_new (label,
2277 GTK_FILE_CHOOSER_ACTION_SAVE,
2278 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2279 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2281 /* add filename suggestions */
2282 if (strlen(def) > 0 )
2283 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2285 //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2289 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2290 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2291 /* activate filter */
2292 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2294 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2299 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2302 f = fopen(filename, openMode);
2305 DisplayError(_("Failed to open file"), errno);
2309 /* TODO add indec */
2311 ASSIGN(*name, filename);
2312 ScheduleDelayedEvent(DelayedLoad, 50);
2317 gtk_widget_destroy (dialog);