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] = name = strdup(strchr(name, ':')+1);
390 fontValid[number][size] = True;
394 case 0: // CLOCK_FONT
395 appData.clockFont = strdup(name);
397 case 1: // MESSAGE_FONT
398 appData.font = strdup(name);
400 case 2: // COORD_FONT
401 appData.coordFont = strdup(name);
404 appData.icsFont = strdup(name);
407 appData.tagsFont = strdup(name);
410 appData.commentFont = strdup(name);
412 case MOVEHISTORY_FONT:
413 appData.historyFont = strdup(name);
416 appData.gameListFont = strdup(name);
421 fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
426 { // only 2 fonts currently
427 appData.clockFont = strdup(CLOCK_FONT_NAME);
428 appData.coordFont = strdup(COORD_FONT_NAME);
429 appData.font = strdup(DEFAULT_FONT_NAME);
430 appData.icsFont = strdup(CONSOLE_FONT_NAME);
431 appData.tagsFont = strdup(TAGS_FONT_NAME);
432 appData.commentFont = strdup(COMMENT_FONT_NAME);
433 appData.historyFont = strdup(HISTORY_FONT_NAME);
434 appData.gameListFont = strdup(GAMELIST_FONT_NAME);
439 { // no-op, until we identify the code for this already in XBoard and move it here
443 ParseColor (int n, char *name)
444 { // in XBoard, just copy the color-name string
445 if(colorVariable[n]) *(char**)colorVariable[n] = strdup(name);
451 return *(char**)colorVariable[n];
455 ParseTextAttribs (ColorClass cc, char *s)
457 (&appData.colorShout)[cc] = strdup(s);
461 ParseBoardSize (void *addr, char *name)
463 appData.boardSize = strdup(name);
468 { // In XBoard the sound-playing program takes care of obtaining the actual sound
472 SetCommPortDefaults ()
473 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
476 // [HGM] args: these three cases taken out to stay in front-end
478 SaveFontArg (FILE *f, ArgDescriptor *ad)
481 int i, n = (int)(intptr_t)ad->argLoc;
483 case 0: // CLOCK_FONT
484 name = appData.clockFont;
486 case 1: // MESSAGE_FONT
489 case 2: // COORD_FONT
490 name = appData.coordFont;
493 name = appData.icsFont;
496 name = appData.tagsFont;
499 name = appData.commentFont;
501 case MOVEHISTORY_FONT:
502 name = appData.historyFont;
505 name = appData.gameListFont;
510 for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
511 if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
512 fontTable[n][squareSize] = strdup(name);
513 fontValid[n][squareSize] = True;
516 for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
517 fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
522 { // nothing to do, as the sounds are at all times represented by their text-string names already
526 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
527 { // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
528 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
532 SaveColor (FILE *f, ArgDescriptor *ad)
533 { // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
534 if(colorVariable[(int)(intptr_t)ad->argLoc])
535 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
539 SaveBoardSize (FILE *f, char *name, void *addr)
540 { // wrapper to shield back-end from BoardSize & sizeInfo
541 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
545 ParseCommPortSettings (char *s)
546 { // no such option in XBoard (yet)
552 GetActualPlacement (GtkWidget *shell, WindowPlacement *wp)
556 gtk_widget_get_allocation(shell, &a);
557 gtk_window_get_position(GTK_WINDOW(shell), &a.x, &a.y);
561 wp->height = a.height;
562 //printf("placement: (%d,%d) %dx%d\n", a.x, a.y, a.width, a.height);
563 frameX = 3; frameY = 3; // remember to decide if windows touch
567 GetPlacement (DialogClass dlg, WindowPlacement *wp)
568 { // wrapper to shield back-end from widget type
569 if(shellUp[dlg]) GetActualPlacement(shells[dlg], wp);
574 { // wrapper to shield use of window handles from back-end (make addressible by number?)
575 // In XBoard this will have to wait until awareness of window parameters is implemented
576 GetActualPlacement(shellWidget, &wpMain);
577 if(shellUp[EngOutDlg]) GetActualPlacement(shells[EngOutDlg], &wpEngineOutput);
578 if(shellUp[HistoryDlg]) GetActualPlacement(shells[HistoryDlg], &wpMoveHistory);
579 if(shellUp[EvalGraphDlg]) GetActualPlacement(shells[EvalGraphDlg], &wpEvalGraph);
580 if(shellUp[GameListDlg]) GetActualPlacement(shells[GameListDlg], &wpGameList);
581 if(shellUp[CommentDlg]) GetActualPlacement(shells[CommentDlg], &wpComment);
582 if(shellUp[TagsDlg]) GetActualPlacement(shells[TagsDlg], &wpTags);
583 GetPlacement(ChatDlg, &wpConsole); if(appData.icsActive) wpConsole.visible = shellUp[ChatDlg];
587 PrintCommPortSettings (FILE *f, char *name)
588 { // This option does not exist in XBoard
592 EnsureOnScreen (int *x, int *y, int minX, int minY)
599 { // [HGM] args: allows testing if main window is realized from back-end
600 return DialogExists(BoardWindow);
604 PopUpStartupDialog ()
605 { // start menu not implemented in XBoard
609 ConvertToLine (int argc, char **argv)
611 static char line[128*1024], buf[1024];
615 for(i=1; i<argc; i++)
617 if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
618 && argv[i][0] != '{' )
619 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
621 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
622 strncat(line, buf, 128*1024 - strlen(line) - 1 );
625 line[strlen(line)-1] = NULLCHAR;
629 //--------------------------------------------------------------------------------------------
634 ResizeBoardWindow (int w, int h, int inhibit)
637 // if(clockKludge) return; // ignore as long as clock does not have final height
638 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
639 w += marginW + 1; // [HGM] not sure why the +1 is (sometimes) needed...
640 h += marginH + a.height + 1;
641 gtk_window_resize(GTK_WINDOW(shellWidget), w, h);
646 { // dummy, as the GTK code does not make colors in advance
651 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
652 { // determine what fonts to use, and create them
654 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
655 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
656 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
657 appData.font = fontTable[MESSAGE_FONT][squareSize];
658 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
659 appData.coordFont = fontTable[COORD_FONT][squareSize];
660 if(!fontIsSet[CONSOLE_FONT] && fontValid[CONSOLE_FONT][squareSize])
661 appData.icsFont = fontTable[CONSOLE_FONT][squareSize];
662 if(!fontIsSet[COMMENT_FONT] && fontValid[COMMENT_FONT][squareSize])
663 appData.commentFont = fontTable[COMMENT_FONT][squareSize];
665 appData.font = InsertPxlSize(appData.font, fontPxlSize);
666 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
667 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
668 appData.icsFont = InsertPxlSize(appData.icsFont, fontPxlSize);
669 appData.tagsFont = InsertPxlSize(appData.tagsFont, fontPxlSize);
670 appData.commentFont = InsertPxlSize(appData.commentFont, fontPxlSize);
671 appData.historyFont = InsertPxlSize(appData.historyFont, fontPxlSize);
672 appData.gameListFont = InsertPxlSize(appData.gameListFont, fontPxlSize);
678 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
679 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
680 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
681 appData.font = fontTable[MESSAGE_FONT][squareSize];
682 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
683 appData.coordFont = fontTable[COORD_FONT][squareSize];
686 appData.font = InsertPxlSize(appData.font, fontPxlSize);
687 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
688 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
689 fontSet = CreateFontSet(appData.font);
690 clockFontSet = CreateFontSet(appData.clockFont);
692 /* For the coordFont, use the 0th font of the fontset. */
693 XFontSet coordFontSet = CreateFontSet(appData.coordFont);
694 XFontStruct **font_struct_list;
695 XFontSetExtents *fontSize;
696 char **font_name_list;
697 XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
698 coordFontID = XLoadFont(xDisplay, font_name_list[0]);
699 coordFontStruct = XQueryFont(xDisplay, coordFontID);
700 fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
701 textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
704 appData.font = FindFont(appData.font, fontPxlSize);
705 appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
706 appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
707 clockFontID = XLoadFont(xDisplay, appData.clockFont);
708 clockFontStruct = XQueryFont(xDisplay, clockFontID);
709 coordFontID = XLoadFont(xDisplay, appData.coordFont);
710 coordFontStruct = XQueryFont(xDisplay, coordFontID);
711 // textHeight in !NLS mode!
713 countFontID = coordFontID; // [HGM] holdings
714 countFontStruct = coordFontStruct;
716 xdb = XtDatabase(xDisplay);
718 XrmPutLineResource(&xdb, "*international: True");
719 vTo.size = sizeof(XFontSet);
720 vTo.addr = (XtPointer) &fontSet;
721 XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
723 XrmPutStringResource(&xdb, "*font", appData.font);
734 case ArgInt: p = " N"; break;
735 case ArgString: p = " STR"; break;
736 case ArgBoolean: p = " TF"; break;
737 case ArgSettingsFilename:
738 case ArgBackupSettingsFile:
739 case ArgFilename: p = " FILE"; break;
740 case ArgX: p = " Nx"; break;
741 case ArgY: p = " Ny"; break;
742 case ArgAttribs: p = " TEXTCOL"; break;
743 case ArgColor: p = " COL"; break;
744 case ArgFont: p = " FONT"; break;
745 case ArgBoardSize: p = " SIZE"; break;
746 case ArgFloat: p = " FLOAT"; break;
751 case ArgCommSettings:
763 ArgDescriptor *q, *p = argDescriptors+5;
764 printf("\nXBoard accepts the following options:\n"
765 "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
766 " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
767 " SIZE = board-size spec(s)\n"
768 " Within parentheses are short forms, or options to set to true or false.\n"
769 " Persistent options (saved in the settings file) are marked with *)\n\n");
771 if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
772 snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
773 if(p->save) strcat(buf+len, "*");
774 for(q=p+1; q->argLoc == p->argLoc; q++) {
775 if(q->argName[0] == '-') continue;
776 strcat(buf+len, q == p+1 ? " (" : " ");
777 sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
779 if(q != p+1) strcat(buf+len, ")");
781 if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
784 if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
788 SlaveResize (Option *opt)
790 static int slaveW, slaveH, w, h;
793 gtk_widget_get_allocation(shells[DummyDlg], &a);
794 w = a.width; h = a.height;
795 gtk_widget_get_allocation(opt->handle, &a);
796 slaveW = w - opt->max; // [HGM] needed to set new shellWidget size when we resize board
797 slaveH = h - a.height + 13;
799 gtk_window_resize(GTK_WINDOW(shells[DummyDlg]), slaveW + opt->max, slaveH + opt->value);
803 static char clickedFile[MSG_SIZ];
807 StartNewXBoard(GtkosxApplication *app, gchar *path, gpointer user_data)
808 { // handler of OSX OpenFile signal, which sends us the filename of clicked file or first argument
809 if(suppress) { // we just started XBoard without arguments
810 strncpy(clickedFile, path, MSG_SIZ); // remember file name, but otherwise ignore
811 } else { // we are running something presumably useful
813 snprintf(buf, MSG_SIZ, "open -n -a \"xboard\" --args \"%s\"", path);
814 system(buf); // start new instance on this file
821 main (int argc, char **argv)
823 int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
824 int boardWidth, w, h; //, boardHeight;
826 int forceMono = False;
828 srandom(time(0)); // [HGM] book: make random truly random
830 setbuf(stdout, NULL);
831 setbuf(stderr, NULL);
834 if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
835 printf("%s version %s\n\n configure options: %s\n", PACKAGE_NAME, PACKAGE_VERSION, CONFIGURE_OPTIONS);
839 if(argc > 1 && !strcmp(argv[1], "--help" )) {
845 gtk_init (&argc, &argv);
847 { // prepare to catch OX OpenFile signal, which will tell us the clicked file
848 GtkosxApplication *theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
849 char *path = gtkosx_application_get_bundle_path();
850 strncpy(dataDir, path, MSG_SIZ);
851 snprintf(masterSettings, MSG_SIZ, "%s/Contents/Resources/etc/xboard.conf", path);
852 g_signal_connect(theApp, "NSApplicationOpenFile", G_CALLBACK(StartNewXBoard), NULL);
853 // we must call application ready before we can get the signal,
854 // and supply a (dummy) menu bar before that, to avoid problems with dual apples in it
855 gtkosx_application_set_menu_bar(theApp, GTK_MENU_SHELL(gtk_menu_bar_new()));
856 gtkosx_application_ready(theApp);
857 suppress = (argc == 1 || argc > 1 && argv[1][00] != '-'); // OSX sends signal even if name was already argv[1]!
858 if(argc == 1) { // called without args: OSX open-file signal might follow
859 static char *fakeArgv[3] = {NULL, clickedFile, NULL};
860 usleep(10000); // wait 10 msec (and hope this is long enough).
861 while(gtk_events_pending())
862 gtk_main_iteration(); // process all events that came in upto now
863 suppress = 0; // future open-file signals should start new instance
864 if(clickedFile[0]) { // we were sent an open-file signal with filename!
865 fakeArgv[0] = argv[0];
866 argc = 2; argv = fakeArgv; // fake that we were called as "xboard filename"
872 if(argc > 1 && !strcmp(argv[1], "--show-config")) { // [HGM] install: called to print config info
873 typedef struct {char *name, *value; } Config;
874 static Config configList[] = {
875 { "Datadir", DATADIR },
876 { "Sysconfdir", SYSCONFDIR },
881 for(i=0; configList[i].name; i++) {
882 if(argc > 2 && strcmp(argv[2], configList[i].name)) continue;
883 if(argc > 2) printf("%s", configList[i].value);
884 else printf("%-12s: %s\n", configList[i].name, configList[i].value);
889 /* set up keyboard accelerators group */
890 GtkAccelerators = gtk_accel_group_new();
892 programName = strrchr(argv[0], '/');
893 if (programName == NULL)
894 programName = argv[0];
899 // if (appData.debugMode) {
900 // fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
903 bindtextdomain(PACKAGE, LOCALEDIR);
904 bind_textdomain_codeset(PACKAGE, "UTF-8"); // needed when creating markup for the clocks
908 appData.boardSize = "";
909 InitAppData(ConvertToLine(argc, argv));
911 if (p == NULL) p = "/tmp";
912 i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
913 gameCopyFilename = (char*) malloc(i);
914 gamePasteFilename = (char*) malloc(i);
915 snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
916 snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
918 { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
919 static char buf[MSG_SIZ];
920 snprintf(buf, MSG_SIZ, appData.sysOpen, DATADIR);
921 ASSIGN(appData.sysOpen, buf); // expand %s in -openCommand to DATADIR (usefull for OS X configuring)
922 EscapeExpand(buf, appData.firstInitString);
923 appData.firstInitString = strdup(buf);
924 EscapeExpand(buf, appData.secondInitString);
925 appData.secondInitString = strdup(buf);
926 EscapeExpand(buf, appData.firstComputerString);
927 appData.firstComputerString = strdup(buf);
928 EscapeExpand(buf, appData.secondComputerString);
929 appData.secondComputerString = strdup(buf);
932 if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
935 if (chdir(chessDir) != 0) {
936 fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
942 if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
943 /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
944 if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL) {
945 printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
948 setbuf(debugFP, NULL);
952 if (appData.debugMode) {
953 fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
957 /* [HGM,HR] make sure board size is acceptable */
958 if(appData.NrFiles > BOARD_FILES ||
959 appData.NrRanks > BOARD_RANKS )
960 DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
963 /* This feature does not work; animation needs a rewrite */
964 appData.highlightDragging = FALSE;
968 gameInfo.variant = StringToVariant(appData.variant);
972 * determine size, based on supplied or remembered -size, or screen size
974 if (isdigit(appData.boardSize[0])) {
975 i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
976 &lineGap, &clockFontPxlSize, &coordFontPxlSize,
977 &fontPxlSize, &smallLayout, &tinyLayout);
979 fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
980 programName, appData.boardSize);
984 squareSize = (squareSize*8 + BOARD_WIDTH/2)/BOARD_WIDTH; // scale height
986 /* Find some defaults; use the nearest known size */
987 SizeDefaults *szd, *nearest;
988 int distance = 99999;
989 nearest = szd = sizeDefaults;
990 while (szd->name != NULL) {
991 if (abs(szd->squareSize - squareSize) < distance) {
993 distance = abs(szd->squareSize - squareSize);
994 if (distance == 0) break;
998 if (i < 2) lineGap = nearest->lineGap;
999 if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
1000 if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
1001 if (i < 5) fontPxlSize = nearest->fontPxlSize;
1002 if (i < 6) smallLayout = nearest->smallLayout;
1003 if (i < 7) tinyLayout = nearest->tinyLayout;
1006 SizeDefaults *szd = sizeDefaults;
1007 if (*appData.boardSize == NULLCHAR) {
1008 GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow)); // TODO: this does not work, as no mainwindow yet
1009 guint screenwidth = gdk_screen_get_width(screen);
1010 guint screenheight = gdk_screen_get_height(screen);
1011 while (screenwidth < (szd->minScreenSize*BOARD_WIDTH + 4)/8 ||
1012 screenheight < (szd->minScreenSize*BOARD_HEIGHT + 4)/8) {
1015 if (szd->name == NULL) szd--;
1016 appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
1018 while (szd->name != NULL &&
1019 StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
1020 if (szd->name == NULL) {
1021 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
1022 programName, appData.boardSize);
1026 squareSize = szd->squareSize;
1027 lineGap = szd->lineGap;
1028 clockFontPxlSize = szd->clockFontPxlSize;
1029 coordFontPxlSize = szd->coordFontPxlSize;
1030 fontPxlSize = szd->fontPxlSize;
1031 smallLayout = szd->smallLayout;
1032 tinyLayout = szd->tinyLayout;
1033 // [HGM] font: use defaults from settings file if available and not overruled
1036 defaultLineGap = lineGap;
1037 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
1039 /* [HR] height treated separately (hacked) */
1040 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
1041 // boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1044 * Determine what fonts to use.
1046 InitializeFonts((2*clockFontPxlSize+1)/3, coordFontPxlSize, fontPxlSize);
1049 * Detect if there are not enough colors available and adapt.
1052 if (DefaultDepth(xDisplay, xScreen) <= 2) {
1053 appData.monoMode = True;
1057 forceMono = MakeColors();
1060 fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1062 appData.monoMode = True;
1065 ParseIcsTextColors();
1071 layoutName = "tinyLayout";
1072 } else if (smallLayout) {
1073 layoutName = "smallLayout";
1075 layoutName = "normalLayout";
1078 wpMain.width = -1; // prevent popup sizes window
1079 optList = BoardPopUp(squareSize, lineGap, (void*)
1089 InitDrawingHandle(optList + W_BOARD);
1090 shellWidget = shells[BoardWindow];
1091 currBoard = &optList[W_BOARD];
1092 boardWidget = optList[W_BOARD].handle;
1093 menuBarWidget = optList[W_MENU].handle;
1094 dropMenu = optList[W_DROP].handle;
1095 titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1097 formWidget = XtParent(boardWidget);
1098 XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1099 XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1100 XtGetValues(optList[W_WHITE].handle, args, 2);
1101 if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1102 XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1103 XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1104 XtGetValues(optList[W_PAUSE].handle, args, 2);
1108 // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1109 // not need to go into InitDrawingSizes().
1113 // add accelerators to main shell
1114 gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1117 * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1119 WhiteIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_white.svg", NULL);
1120 BlackIcon = gdk_pixbuf_new_from_file(SVGDIR "/icon_black.svg", NULL);
1121 mainwindowIcon = WhiteIcon;
1122 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
1126 * Create a cursor for the board widget.
1129 window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1130 XChangeWindowAttributes(xDisplay, xBoardWindow,
1131 CWCursor, &window_attributes);
1135 * Inhibit shell resizing.
1138 shellArgs[0].value = (XtArgVal) &w;
1139 shellArgs[1].value = (XtArgVal) &h;
1140 XtGetValues(shellWidget, shellArgs, 2);
1141 shellArgs[4].value = shellArgs[2].value = w;
1142 shellArgs[5].value = shellArgs[3].value = h;
1143 // XtSetValues(shellWidget, &shellArgs[2], 4);
1146 // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1147 // It wil only become known asynchronously, when we first write a string into it.
1148 // This will then change the clock widget height, which triggers resizing the top-level window
1149 // and a configure event. Only then can we know the total height of the top-level window,
1150 // and calculate the height we need. The clockKludge flag suppresses all resizing until
1151 // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1154 gtk_widget_get_allocation(shells[BoardWindow], &a);
1155 w = a.width; h = a.height;
1156 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1157 clockKludge = hc = a.height;
1158 gtk_widget_get_allocation(boardWidget, &a);
1159 marginW = w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1160 marginH = h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1166 if(appData.logoSize)
1167 { // locate and read user logo
1169 snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1170 ASSIGN(userLogo, buf);
1173 if (appData.animate || appData.animateDragging)
1176 g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1177 g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1179 /* [AS] Restore layout */
1180 if( wpMoveHistory.visible ) {
1184 if( wpEvalGraph.visible )
1189 if( wpEngineOutput.visible ) {
1190 EngineOutputPopUp();
1193 if( wpConsole.visible && appData.icsActive ) {
1198 gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1203 if (errorExitStatus == -1) {
1204 if (appData.icsActive) {
1205 /* We now wait until we see "login:" from the ICS before
1206 sending the logon script (problems with timestamp otherwise) */
1207 /*ICSInitScript();*/
1208 if (appData.icsInputBox) ICSInputBoxPopUp();
1212 signal(SIGWINCH, TermSizeSigHandler);
1214 signal(SIGINT, IntSigHandler);
1215 signal(SIGTERM, IntSigHandler);
1216 if (*appData.cmailGameName != NULLCHAR) {
1217 signal(SIGUSR1, CmailSigHandler);
1222 // XtSetKeyboardFocus(shellWidget, formWidget);
1224 XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1227 /* check for GTK events and process them */
1230 gtk_main_iteration();
1233 if (appData.debugMode) fclose(debugFP); // [DM] debug
1240 while(gtk_events_pending()) gtk_main_iteration();
1244 TermSizeSigHandler (int sig)
1250 IntSigHandler (int sig)
1256 CmailSigHandler (int sig)
1261 signal(SIGUSR1, SIG_IGN); /* suspend handler */
1263 /* Activate call-back function CmailSigHandlerCallBack() */
1264 OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1266 signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1270 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1273 ReloadCmailMsgEvent(TRUE); /* Reload cmail msg */
1275 /**** end signal code ****/
1278 #define Abs(n) ((n)<0 ? -(n) : (n))
1281 InsertPxlSize (char *pattern, int targetPxlSize)
1284 snprintf(buf, MSG_SIZ, pattern, targetPxlSize); // pattern is something like "Sans Bold %d"
1291 InsertPxlSize (char *pattern, int targetPxlSize)
1293 char *base_fnt_lst, strInt[12], *p, *q;
1294 int alternatives, i, len, strIntLen;
1297 * Replace the "*" (if present) in the pixel-size slot of each
1298 * alternative with the targetPxlSize.
1302 while ((p = strchr(p, ',')) != NULL) {
1306 snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1307 strIntLen = strlen(strInt);
1308 base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1312 while (alternatives--) {
1313 char *comma = strchr(p, ',');
1314 for (i=0; i<14; i++) {
1315 char *hyphen = strchr(p, '-');
1317 if (comma && hyphen > comma) break;
1318 len = hyphen + 1 - p;
1319 if (i == 7 && *p == '*' && len == 2) {
1321 memcpy(q, strInt, strIntLen);
1331 len = comma + 1 - p;
1338 return base_fnt_lst;
1344 CreateFontSet (char *base_fnt_lst)
1347 char **missing_list;
1351 fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1352 &missing_list, &missing_count, &def_string);
1353 if (appData.debugMode) {
1355 XFontStruct **font_struct_list;
1356 char **font_name_list;
1357 fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1359 fprintf(debugFP, " got list %s, locale %s\n",
1360 XBaseFontNameListOfFontSet(fntSet),
1361 XLocaleOfFontSet(fntSet));
1362 count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1363 for (i = 0; i < count; i++) {
1364 fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1367 for (i = 0; i < missing_count; i++) {
1368 fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1371 if (fntSet == NULL) {
1372 fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1378 #else // not ENABLE_NLS
1380 * Find a font that matches "pattern" that is as close as
1381 * possible to the targetPxlSize. Prefer fonts that are k
1382 * pixels smaller to fonts that are k pixels larger. The
1383 * pattern must be in the X Consortium standard format,
1384 * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1385 * The return value should be freed with XtFree when no
1390 FindFont (char *pattern, int targetPxlSize)
1392 char **fonts, *p, *best, *scalable, *scalableTail;
1393 int i, j, nfonts, minerr, err, pxlSize;
1395 fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1397 fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1398 programName, pattern);
1405 for (i=0; i<nfonts; i++) {
1408 if (*p != '-') continue;
1410 if (*p == NULLCHAR) break;
1411 if (*p++ == '-') j++;
1413 if (j < 7) continue;
1416 scalable = fonts[i];
1419 err = pxlSize - targetPxlSize;
1420 if (Abs(err) < Abs(minerr) ||
1421 (minerr > 0 && err < 0 && -err == minerr)) {
1427 if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1428 /* If the error is too big and there is a scalable font,
1429 use the scalable font. */
1430 int headlen = scalableTail - scalable;
1431 p = (char *) XtMalloc(strlen(scalable) + 10);
1432 while (isdigit(*scalableTail)) scalableTail++;
1433 sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1435 p = (char *) XtMalloc(strlen(best) + 2);
1436 safeStrCpy(p, best, strlen(best)+1 );
1438 if (appData.debugMode) {
1439 fprintf(debugFP, "resolved %s at pixel size %d\n to %s\n",
1440 pattern, targetPxlSize, p);
1442 XFreeFontNames(fonts);
1449 EnableNamedMenuItem (char *menuRef, int state)
1451 MenuItem *item = MenuNameToItem(menuRef);
1453 if(item && item->handle) gtk_widget_set_sensitive(item->handle, state);
1457 EnableButtonBar (int state)
1460 XtSetSensitive(optList[W_BUTTON].handle, state);
1466 SetMenuEnables (Enables *enab)
1468 while (enab->name != NULL) {
1469 EnableNamedMenuItem(enab->name, enab->value);
1474 gboolean KeyPressProc(window, eventkey, data)
1476 GdkEventKey *eventkey;
1480 MoveTypeInProc(eventkey); // pop up for typed in moves
1483 /* check for other key values */
1484 switch(eventkey->keyval) {
1496 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1497 { // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1499 if(*nprms == 0) return;
1500 item = MenuNameToItem(prms[0]);
1501 if(item) ((MenuProc *) item->proc) ();
1515 for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1516 entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1517 p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1518 dmEnables[i].piece);
1519 XtSetSensitive(entry, p != NULL || !appData.testLegality
1520 /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1521 && !appData.icsActive));
1523 while (p && *p++ == dmEnables[i].piece) count++;
1524 snprintf(label, sizeof(label), "%s %d", dmEnables[i].widget, count);
1526 XtSetArg(args[j], XtNlabel, label); j++;
1527 XtSetValues(entry, args, j);
1533 do_flash_delay (unsigned long msec)
1539 FlashDelay (int flash_delay)
1541 if(flash_delay) do_flash_delay(flash_delay);
1545 Fraction (int x, int start, int stop)
1547 double f = ((double) x - start)/(stop - start);
1548 if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1552 static WindowPlacement wpNew;
1555 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1557 int touch=0, fudge = 2, f = 2;
1558 GetActualPlacement(sh, wp);
1559 if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x) < fudge) touch = 1; else // right touch
1560 if(abs(wp->x + wp->width + 2*frameX + f - wpMain.x) < fudge) touch = 2; else // left touch
1561 if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1562 if(abs(wp->y + wp->height + frameX + frameY + f - wpMain.y) < fudge) touch = 4; // top touch
1563 //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);
1564 if(!touch ) return; // only windows that touch co-move
1565 if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1566 int heightInc = wpNew.height - wpMain.height;
1567 double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1568 double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1569 wp->y += fracTop * heightInc;
1570 heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1572 if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1574 wp->height += heightInc;
1575 } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1576 int widthInc = wpNew.width - wpMain.width;
1577 double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1578 double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1579 wp->y += fracLeft * widthInc;
1580 widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1582 if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1584 wp->width += widthInc;
1586 wp->x += wpNew.x - wpMain.x;
1587 wp->y += wpNew.y - wpMain.y;
1588 if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1589 if(touch == 3) wp->y += wpNew.height - wpMain.height;
1591 XtSetArg(args[j], XtNx, wp->x); j++;
1592 XtSetArg(args[j], XtNy, wp->y); j++;
1593 XtSetValues(sh, args, j);
1595 gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1596 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1597 gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1601 ReSize (WindowPlacement *wp)
1604 int sqx, sqy, w, h, hc, lg = lineGap;
1605 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1606 hc = a.height; // clock height can depend on single / double line clock text!
1607 if(clockKludge && hc != clockKludge) wp->height += hc - clockKludge, clockKludge = 0;
1608 wpMain.height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1609 if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1610 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1611 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1612 if(sqy < sqx) sqx = sqy;
1613 if(sqx < 20) return;
1614 if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1615 lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1616 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1617 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1618 if(sqy < sqx) sqx = sqy;
1620 if(sqx != squareSize) {
1621 squareSize = sqx; // adopt new square size
1622 CreatePNGPieces(); // make newly scaled pieces
1623 InitDrawingSizes(0, 0); // creates grid etc.
1624 } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1625 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1626 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1627 if(optList[W_BOARD].max > w) optList[W_BOARD].max = w;
1628 if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1631 static guint delayedDragTag = 0;
1640 // GetActualPlacement(shellWidget, &wpNew);
1641 if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1642 wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1643 busy = 0; return; // false alarm
1646 if(appData.useStickyWindows) {
1647 if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1648 if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1649 if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1650 if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1651 if(shellUp[ChatDlg]) CoDrag(shells[ChatDlg], &wpConsole);
1654 DrawPosition(True, NULL);
1655 if(delayedDragTag) g_source_remove(delayedDragTag);
1656 delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1663 //printf("old timr = %d\n", delayedDragTag);
1664 if(delayedDragTag) g_source_remove(delayedDragTag);
1665 delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1666 //printf("new timr = %d\n", delayedDragTag);
1670 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1672 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1674 wpNew.x = event->configure.x;
1675 wpNew.y = event->configure.y;
1676 wpNew.width = event->configure.width;
1677 wpNew.height = event->configure.height;
1678 DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1684 /* Disable all user input other than deleting the window */
1685 static int frozen = 0;
1691 /* Grab by a widget that doesn't accept input */
1692 gtk_grab_add(optList[W_MESSG].handle);
1696 /* Undo a FreezeUI */
1700 if (!frozen) return;
1701 gtk_grab_remove(optList[W_MESSG].handle);
1708 static int oldPausing = FALSE;
1709 static GameMode oldmode = (GameMode) -1;
1711 if (!boardWidget) return;
1713 if (pausing != oldPausing) {
1714 oldPausing = pausing;
1715 MarkMenuItem("Mode.Pause", pausing);
1717 if (appData.showButtonBar) {
1718 /* Always toggle, don't set. Previous code messes up when
1719 invoked while the button is pressed, as releasing it
1720 toggles the state again. */
1722 gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1723 gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1727 wname = ModeToWidgetName(oldmode);
1728 if (wname != NULL) {
1729 MarkMenuItem(wname, False);
1731 wname = ModeToWidgetName(gameMode);
1732 if (wname != NULL) {
1733 MarkMenuItem(wname, True);
1736 MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1738 /* Maybe all the enables should be handled here, not just this one */
1739 EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1741 DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1746 * Button/menu procedures
1749 void CopyFileToClipboard(gchar *filename)
1751 gchar *selection_tmp;
1755 FILE* f = fopen(filename, "r");
1758 if (f == NULL) return;
1762 selection_tmp = g_try_malloc(len + 1);
1763 if (selection_tmp == NULL) {
1764 printf("Malloc failed in CopyFileToClipboard\n");
1767 count = fread(selection_tmp, 1, len, f);
1770 g_free(selection_tmp);
1773 selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1775 // copy selection_tmp to clipboard
1776 GdkDisplay *gdisp = gdk_display_get_default();
1778 g_free(selection_tmp);
1781 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1782 gtk_clipboard_set_text(cb, selection_tmp, -1);
1783 g_free(selection_tmp);
1787 CopySomething (char *src)
1789 GdkDisplay *gdisp = gdk_display_get_default();
1791 if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1792 if (gdisp == NULL) return;
1793 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1794 gtk_clipboard_set_text(cb, src, -1);
1798 PastePositionProc ()
1800 GdkDisplay *gdisp = gdk_display_get_default();
1804 if (gdisp == NULL) return;
1805 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1806 fenstr = gtk_clipboard_wait_for_text(cb);
1807 if (fenstr==NULL) return; // nothing had been selected to copy
1808 EditPositionPasteFEN(fenstr);
1820 // get game from clipboard
1821 GdkDisplay *gdisp = gdk_display_get_default();
1822 if (gdisp == NULL) return;
1823 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1824 text = gtk_clipboard_wait_for_text(cb);
1825 if (text == NULL) return; // nothing to paste
1828 // write to temp file
1829 if (text == NULL || len == 0) {
1830 return; //nothing to paste
1832 f = fopen(gamePasteFilename, "w");
1834 DisplayError(_("Can't open temp file"), errno);
1837 fwrite(text, 1, len, f);
1841 LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1848 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1854 void MoveTypeInProc(eventkey)
1855 GdkEventKey *eventkey;
1859 // ingnore if ctrl, alt, or meta is pressed
1860 if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1864 buf[0]=eventkey->keyval;
1866 if (eventkey->keyval > 32 && eventkey->keyval < 256)
1867 ConsoleAutoPopUp (buf);
1872 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1874 if (!TempBackwardActive) {
1875 TempBackwardActive = True;
1881 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1883 /* Check to see if triggered by a key release event for a repeating key.
1884 * If so the next queued event will be a key press of the same key at the same time */
1885 if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1887 XPeekEvent(xDisplay, &next);
1888 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1889 next.xkey.keycode == event->xkey.keycode)
1893 TempBackwardActive = False;
1899 { // called from menu
1902 snprintf(buf, MSG_SIZ, "%s ./man.command", appData.sysOpen);
1905 system("xterm -e man xboard &");
1910 SetWindowTitle (char *text, char *title, char *icon)
1915 if (appData.titleInWindow) {
1917 XtSetArg(args[i], XtNlabel, text); i++;
1918 XtSetValues(titleWidget, args, i);
1921 XtSetArg(args[i], XtNiconName, (XtArgVal) icon); i++;
1922 XtSetArg(args[i], XtNtitle, (XtArgVal) title); i++;
1923 XtSetValues(shellWidget, args, i);
1924 XSync(xDisplay, False);
1926 if (appData.titleInWindow) {
1927 SetWidgetLabel(titleWidget, text);
1929 gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1934 DisplayIcsInteractionTitle (String message)
1937 if (oldICSInteractionTitle == NULL) {
1938 /* Magic to find the old window title, adapted from vim */
1939 char *wina = getenv("WINDOWID");
1941 Window win = (Window) atoi(wina);
1942 Window root, parent, *children;
1943 unsigned int nchildren;
1944 int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
1946 if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
1947 if (!XQueryTree(xDisplay, win, &root, &parent,
1948 &children, &nchildren)) break;
1949 if (children) XFree((void *)children);
1950 if (parent == root || parent == 0) break;
1953 XSetErrorHandler(oldHandler);
1955 if (oldICSInteractionTitle == NULL) {
1956 oldICSInteractionTitle = "xterm";
1959 printf("\033]0;%s\007", message);
1966 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
1968 GtkWidget *w = (GtkWidget *) opt->handle;
1975 strcpy(bgcolor, "black");
1976 strcpy(fgcolor, "white");
1978 strcpy(bgcolor, "white");
1979 strcpy(fgcolor, "black");
1982 appData.lowTimeWarning &&
1983 (timer / 1000) < appData.icsAlarmTime) {
1984 strcpy(fgcolor, appData.lowTimeWarningColor);
1987 gdk_color_parse( bgcolor, &col );
1988 gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
1990 if (appData.clockMode) {
1991 markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>", appData.clockFont,
1992 bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
1993 // markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
1994 // bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
1996 markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s </span>", appData.clockFont,
1997 bgcolor, fgcolor, color);
1998 // markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s </span>",
1999 // bgcolor, fgcolor, color);
2001 gtk_label_set_markup(GTK_LABEL(w), markup);
2005 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
2008 SetClockIcon (int color)
2010 GdkPixbuf *pm = *clockIcons[color];
2011 if (mainwindowIcon != pm) {
2012 mainwindowIcon = pm;
2013 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
2017 #define INPUT_SOURCE_BUF_SIZE 8192
2026 char buf[INPUT_SOURCE_BUF_SIZE];
2031 DoInputCallback(io, cond, data)
2036 /* read input from one of the input source (for example a chess program, ICS, etc).
2037 * and call a function that will handle the input
2044 /* All information (callback function, file descriptor, etc) is
2045 * saved in an InputSource structure
2047 InputSource *is = (InputSource *) data;
2049 if (is->lineByLine) {
2050 count = read(is->fd, is->unused,
2051 INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
2053 if(count == 0 && is->kind == CPReal && shells[ChatDlg]) { // [HGM] absence of terminal is no error if ICS Console present
2054 RemoveInputSource(is); // cease reading stdin
2055 stdoutClosed = TRUE; // suppress future output
2058 (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
2061 is->unused += count;
2063 /* break input into lines and call the callback function on each
2066 while (p < is->unused) {
2067 q = memchr(p, '\n', is->unused - p);
2068 if (q == NULL) break;
2070 (is->func)(is, is->closure, p, q - p, 0);
2073 /* remember not yet used part of the buffer */
2075 while (p < is->unused) {
2080 /* read maximum length of input buffer and send the whole buffer
2081 * to the callback function
2083 count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2088 (is->func)(is, is->closure, is->buf, count, error);
2090 return True; // Must return true or the watch will be removed
2093 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2100 GIOChannel *channel;
2101 ChildProc *cp = (ChildProc *) pr;
2103 is = (InputSource *) calloc(1, sizeof(InputSource));
2104 is->lineByLine = lineByLine;
2108 is->fd = fileno(stdin);
2110 is->kind = cp->kind;
2111 is->fd = cp->fdFrom;
2114 is->unused = is->buf;
2118 /* GTK-TODO: will this work on windows?*/
2120 channel = g_io_channel_unix_new(is->fd);
2121 g_io_channel_set_close_on_unref (channel, TRUE);
2122 is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2124 is->closure = closure;
2125 return (InputSourceRef) is;
2130 RemoveInputSource(isr)
2133 InputSource *is = (InputSource *) isr;
2135 if (is->sid == 0) return;
2136 g_source_remove(is->sid);
2143 static Boolean frameWaiting;
2146 FrameAlarm (int sig)
2148 frameWaiting = False;
2149 /* In case System-V style signals. Needed?? */
2150 signal(SIGALRM, FrameAlarm);
2154 FrameDelay (int time)
2156 struct itimerval delay;
2159 frameWaiting = True;
2160 signal(SIGALRM, FrameAlarm);
2161 delay.it_interval.tv_sec =
2162 delay.it_value.tv_sec = time / 1000;
2163 delay.it_interval.tv_usec =
2164 delay.it_value.tv_usec = (time % 1000) * 1000;
2165 setitimer(ITIMER_REAL, &delay, NULL);
2166 while (frameWaiting) pause();
2167 delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2168 delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2169 setitimer(ITIMER_REAL, &delay, NULL);
2176 FrameDelay (int time)
2179 XSync(xDisplay, False);
2181 // gtk_main_iteration_do(False);
2184 usleep(time * 1000);
2190 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2192 char buf[MSG_SIZ], *logoName = buf;
2193 if(appData.logo[n][0]) {
2194 logoName = appData.logo[n];
2195 } else if(appData.autoLogo) {
2196 if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2197 sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2198 } else if(appData.directory[n] && appData.directory[n][0]) {
2199 sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2203 { ASSIGN(cps->programLogo, logoName); }
2207 UpdateLogos (int displ)
2209 if(optList[W_WHITE-1].handle == NULL) return;
2210 LoadLogo(&first, 0, 0);
2211 LoadLogo(&second, 1, appData.icsActive);
2212 if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2216 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2227 GtkFileFilter *gtkfilter;
2228 GtkFileFilter *gtkfilter_all;
2230 char fileext[10] = "";
2231 char *result = NULL;
2234 /* make a copy of the filter string, so that strtok can work with it*/
2235 cp = strdup(filter);
2237 /* add filters for file extensions */
2238 gtkfilter = gtk_file_filter_new();
2239 gtkfilter_all = gtk_file_filter_new();
2241 /* one filter to show everything */
2242 gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2243 gtk_file_filter_set_name (gtkfilter_all, "All Files");
2245 /* add filter if present */
2246 result = strtok(cp, space);
2247 while( result != NULL ) {
2248 snprintf(fileext,10,"*%s",result);
2249 result = strtok( NULL, space );
2250 gtk_file_filter_add_pattern(gtkfilter, fileext);
2253 /* second filter to only show what's useful */
2254 gtk_file_filter_set_name (gtkfilter,filter);
2256 if (openMode[0] == 'r')
2258 dialog = gtk_file_chooser_dialog_new (label,
2260 GTK_FILE_CHOOSER_ACTION_OPEN,
2261 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2262 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2267 dialog = gtk_file_chooser_dialog_new (label,
2269 GTK_FILE_CHOOSER_ACTION_SAVE,
2270 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2271 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2273 /* add filename suggestions */
2274 if (strlen(def) > 0 )
2275 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2277 //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2281 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2282 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2283 /* activate filter */
2284 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2286 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2291 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2294 f = fopen(filename, openMode);
2297 DisplayError(_("Failed to open file"), errno);
2301 /* TODO add indec */
2303 ASSIGN(*name, filename);
2304 ScheduleDelayedEvent(DelayedLoad, 50);
2309 gtk_widget_destroy (dialog);