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).
176 // redefine some defaults
179 # undef SETTINGS_FILE
180 # define ICS_LOGON "Library/Preferences/XboardICS.conf"
181 # define DATADIR dataDir
182 # define SETTINGS_FILE masterSettings
183 char dataDir[MSG_SIZ]; // for expanding ~~
184 char masterSettings[MSG_SIZ];
194 #define usleep(t) _sleep2(((t)+500)/1000)
198 # define _(s) gettext (s)
199 # define N_(s) gettext_noop (s)
205 int main P((int argc, char **argv));
206 RETSIGTYPE CmailSigHandler P((int sig));
207 RETSIGTYPE IntSigHandler P((int sig));
208 RETSIGTYPE TermSizeSigHandler P((int sig));
209 char *InsertPxlSize P((char *pattern, int targetPxlSize));
211 XFontSet CreateFontSet P((char *base_fnt_lst));
213 char *FindFont P((char *pattern, int targetPxlSize));
215 void DelayedDrag P((void));
216 void ICSInputBoxPopUp P((void));
217 void MoveTypeInProc P((GdkEventKey *eventkey));
218 gboolean KeyPressProc P((GtkWindow *window, GdkEventKey *eventkey, gpointer data));
219 Boolean TempBackwardActive = False;
220 void DisplayMove P((int moveNumber));
221 void update_ics_width P(());
222 int CopyMemoProc P(());
223 static gboolean EventProc P((GtkWidget *widget, GdkEvent *event, gpointer g));
227 XFontSet fontSet, clockFontSet;
230 XFontStruct *clockFontStruct;
232 Font coordFontID, countFontID;
233 XFontStruct *coordFontStruct, *countFontStruct;
235 void *shellWidget, *formWidget, *boardWidget, *titleWidget, *dropMenu, *menuBarWidget;
236 GtkWidget *mainwindow;
238 Option *optList; // contains all widgets of main window
241 char installDir[] = "."; // [HGM] UCI: needed for UCI; probably needs run-time initializtion
244 static GdkPixbuf *mainwindowIcon=NULL;
245 static GdkPixbuf *WhiteIcon=NULL;
246 static GdkPixbuf *BlackIcon=NULL;
248 /* key board accelerators */
249 GtkAccelGroup *GtkAccelerators;
251 typedef unsigned int BoardSize;
253 Boolean chessProgram;
255 int minX, minY; // [HGM] placement: volatile limits on upper-left corner
256 int smallLayout = 0, tinyLayout = 0,
257 marginW, marginH, // [HGM] for run-time resizing
258 fromX = -1, fromY = -1, toX, toY, commentUp = False,
259 errorExitStatus = -1, defaultLineGap;
261 Dimension textHeight;
263 char *chessDir, *programName, *programVersion;
264 Boolean alwaysOnTop = False;
265 char *icsTextMenuString;
267 char *firstChessProgramNames;
268 char *secondChessProgramNames;
270 WindowPlacement wpMain;
271 WindowPlacement wpConsole;
272 WindowPlacement wpComment;
273 WindowPlacement wpMoveHistory;
274 WindowPlacement wpEvalGraph;
275 WindowPlacement wpEngineOutput;
276 WindowPlacement wpGameList;
277 WindowPlacement wpTags;
278 WindowPlacement wpDualBoard;
280 /* This magic number is the number of intermediate frames used
281 in each half of the animation. For short moves it's reduced
282 by 1. The total number of frames will be factor * 2 + 1. */
285 SizeDefaults sizeDefaults[] = SIZE_DEFAULTS;
292 DropMenuEnables dmEnables[] = {
301 XtResource clientResources[] = {
302 { "flashCount", "flashCount", XtRInt, sizeof(int),
303 XtOffset(AppDataPtr, flashCount), XtRImmediate,
304 (XtPointer) FLASH_COUNT },
308 /* keyboard shortcuts not yet transistioned int menuitem @ menu.c */
309 char globalTranslations[] =
310 ":Ctrl<Key>Down: LoadSelectedProc(3) \n \
311 :Ctrl<Key>Up: LoadSelectedProc(-3) \n \
312 :<KeyDown>Return: TempBackwardProc() \n \
313 :<KeyUp>Return: TempForwardProc() \n";
315 char ICSInputTranslations[] =
316 "<Key>Up: UpKeyProc() \n "
317 "<Key>Down: DownKeyProc() \n "
318 "<Key>Return: EnterKeyProc() \n";
320 // [HGM] vari: another hideous kludge: call extend-end first so we can be sure select-start works,
321 // as the widget is destroyed before the up-click can call extend-end
322 char commentTranslations[] = "<Btn3Down>: extend-end() select-start() CommentClick() \n";
325 String xboardResources[] = {
326 "*Error*translations: #override\\n <Key>Return: ErrorPopDown()",
334 gtk_window_present(GTK_WINDOW(shells[BoardWindow]));
337 //---------------------------------------------------------------------------------------------------------
338 // some symbol definitions to provide the proper (= XBoard) context for the code in args.h
341 #define CW_USEDEFAULT (1<<31)
342 #define ICS_TEXT_MENU_SIZE 90
343 #define DEBUG_FILE "xboard.debug"
344 #define SetCurrentDirectory chdir
345 #define GetCurrentDirectory(SIZE, NAME) getcwd(NAME, SIZE)
349 // The option definition and parsing code common to XBoard and WinBoard is collected in this file
352 // front-end part of option handling
354 // [HGM] This platform-dependent table provides the location for storing the color info
355 extern char *crWhite, * crBlack;
359 &appData.whitePieceColor,
360 &appData.blackPieceColor,
361 &appData.lightSquareColor,
362 &appData.darkSquareColor,
363 &appData.highlightSquareColor,
364 &appData.premoveHighlightColor,
365 &appData.lowTimeWarningColor,
376 // [HGM] font: keep a font for each square size, even non-stndard ones
379 Boolean fontIsSet[NUM_FONTS], fontValid[NUM_FONTS][MAX_SIZE];
380 char *fontTable[NUM_FONTS][MAX_SIZE];
383 ParseFont (char *name, int number)
384 { // in XBoard, only 2 of the fonts are currently implemented, and we just copy their name
386 if(sscanf(name, "size%d:", &size)) {
387 // [HGM] font: font is meant for specific boardSize (likely from settings file);
388 // defer processing it until we know if it matches our board size
389 if(!strstr(name, "-*-") && // ignore X-fonts
390 size >= 0 && size<MAX_SIZE) { // for now, fixed limit
391 fontTable[number][size] = strdup(strchr(name, ':')+1);
392 fontValid[number][size] = True;
397 case 0: // CLOCK_FONT
398 appData.clockFont = strdup(name);
400 case 1: // MESSAGE_FONT
401 appData.font = strdup(name);
403 case 2: // COORD_FONT
404 appData.coordFont = strdup(name);
407 appData.icsFont = strdup(name);
410 appData.tagsFont = strdup(name);
413 appData.commentFont = strdup(name);
415 case MOVEHISTORY_FONT:
416 appData.historyFont = strdup(name);
419 appData.gameListFont = strdup(name);
424 fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
429 { // only 2 fonts currently
430 appData.clockFont = strdup(CLOCK_FONT_NAME);
431 appData.coordFont = strdup(COORD_FONT_NAME);
432 appData.font = strdup(DEFAULT_FONT_NAME);
433 appData.icsFont = strdup(CONSOLE_FONT_NAME);
434 appData.tagsFont = strdup(TAGS_FONT_NAME);
435 appData.commentFont = strdup(COMMENT_FONT_NAME);
436 appData.historyFont = strdup(HISTORY_FONT_NAME);
437 appData.gameListFont = strdup(GAMELIST_FONT_NAME);
442 { // no-op, until we identify the code for this already in XBoard and move it here
446 ParseColor (int n, char *name)
447 { // in XBoard, just copy the color-name string
448 if(colorVariable[n]) *(char**)colorVariable[n] = strdup(name);
454 return *(char**)colorVariable[n];
458 ParseTextAttribs (ColorClass cc, char *s)
460 (&appData.colorShout)[cc] = strdup(s);
464 ParseBoardSize (void *addr, char *name)
466 appData.boardSize = strdup(name);
471 { // In XBoard the sound-playing program takes care of obtaining the actual sound
475 SetCommPortDefaults ()
476 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
479 // [HGM] args: these three cases taken out to stay in front-end
481 SaveFontArg (FILE *f, ArgDescriptor *ad)
484 int i, n = (int)(intptr_t)ad->argLoc;
486 case 0: // CLOCK_FONT
487 name = appData.clockFont;
489 case 1: // MESSAGE_FONT
492 case 2: // COORD_FONT
493 name = appData.coordFont;
496 name = appData.icsFont;
499 name = appData.tagsFont;
502 name = appData.commentFont;
504 case MOVEHISTORY_FONT:
505 name = appData.historyFont;
508 name = appData.gameListFont;
513 for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
514 if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
515 fontTable[n][squareSize] = strdup(name);
516 fontValid[n][squareSize] = True;
519 for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
520 fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
525 { // nothing to do, as the sounds are at all times represented by their text-string names already
529 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
530 { // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
531 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
535 SaveColor (FILE *f, ArgDescriptor *ad)
536 { // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
537 if(colorVariable[(int)(intptr_t)ad->argLoc])
538 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
542 SaveBoardSize (FILE *f, char *name, void *addr)
543 { // wrapper to shield back-end from BoardSize & sizeInfo
544 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
548 ParseCommPortSettings (char *s)
549 { // no such option in XBoard (yet)
555 GetActualPlacement (GtkWidget *shell, WindowPlacement *wp)
559 gtk_widget_get_allocation(shell, &a);
560 gtk_window_get_position(GTK_WINDOW(shell), &a.x, &a.y);
564 wp->height = a.height;
565 //printf("placement: (%d,%d) %dx%d\n", a.x, a.y, a.width, a.height);
566 frameX = 3; frameY = 3; // remember to decide if windows touch
570 GetPlacement (DialogClass dlg, WindowPlacement *wp)
571 { // wrapper to shield back-end from widget type
572 if(shellUp[dlg]) GetActualPlacement(shells[dlg], wp);
577 { // wrapper to shield use of window handles from back-end (make addressible by number?)
578 // In XBoard this will have to wait until awareness of window parameters is implemented
579 GetActualPlacement(shellWidget, &wpMain);
580 if(shellUp[EngOutDlg]) GetActualPlacement(shells[EngOutDlg], &wpEngineOutput);
581 if(shellUp[HistoryDlg]) GetActualPlacement(shells[HistoryDlg], &wpMoveHistory);
582 if(shellUp[EvalGraphDlg]) GetActualPlacement(shells[EvalGraphDlg], &wpEvalGraph);
583 if(shellUp[GameListDlg]) GetActualPlacement(shells[GameListDlg], &wpGameList);
584 if(shellUp[CommentDlg]) GetActualPlacement(shells[CommentDlg], &wpComment);
585 if(shellUp[TagsDlg]) GetActualPlacement(shells[TagsDlg], &wpTags);
586 GetPlacement(ChatDlg, &wpConsole); if(appData.icsActive) wpConsole.visible = shellUp[ChatDlg];
590 PrintCommPortSettings (FILE *f, char *name)
591 { // This option does not exist in XBoard
595 EnsureOnScreen (int *x, int *y, int minX, int minY)
602 { // [HGM] args: allows testing if main window is realized from back-end
603 return DialogExists(BoardWindow);
607 PopUpStartupDialog ()
608 { // start menu not implemented in XBoard
612 ConvertToLine (int argc, char **argv)
614 static char line[128*1024], buf[1024];
618 for(i=1; i<argc; i++)
620 if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
621 && argv[i][0] != '{' )
622 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
624 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
625 strncat(line, buf, 128*1024 - strlen(line) - 1 );
628 line[strlen(line)-1] = NULLCHAR;
632 //--------------------------------------------------------------------------------------------
637 ResizeBoardWindow (int w, int h, int inhibit)
640 // if(clockKludge) return; // ignore as long as clock does not have final height
641 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
642 w += marginW + 1; // [HGM] not sure why the +1 is (sometimes) needed...
643 h += marginH + a.height + 1;
644 gtk_window_resize(GTK_WINDOW(shellWidget), w, h);
649 { // dummy, as the GTK code does not make colors in advance
654 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
655 { // determine what fonts to use, and create them
657 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
658 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
659 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
660 appData.font = fontTable[MESSAGE_FONT][squareSize];
661 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
662 appData.coordFont = fontTable[COORD_FONT][squareSize];
663 if(!fontIsSet[CONSOLE_FONT] && fontValid[CONSOLE_FONT][squareSize])
664 appData.icsFont = fontTable[CONSOLE_FONT][squareSize];
665 if(!fontIsSet[EDITTAGS_FONT] && fontValid[EDITTAGS_FONT][squareSize])
666 appData.tagsFont = fontTable[EDITTAGS_FONT][squareSize];
667 if(!fontIsSet[COMMENT_FONT] && fontValid[COMMENT_FONT][squareSize])
668 appData.commentFont = fontTable[COMMENT_FONT][squareSize];
669 if(!fontIsSet[MOVEHISTORY_FONT] && fontValid[MOVEHISTORY_FONT][squareSize])
670 appData.historyFont = fontTable[MOVEHISTORY_FONT][squareSize];
671 if(!fontIsSet[GAMELIST_FONT] && fontValid[GAMELIST_FONT][squareSize])
672 appData.gameListFont = fontTable[GAMELIST_FONT][squareSize];
674 appData.font = InsertPxlSize(appData.font, coordFontPxlSize);
675 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
676 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
677 appData.icsFont = InsertPxlSize(appData.icsFont, coordFontPxlSize);
678 appData.tagsFont = InsertPxlSize(appData.tagsFont, coordFontPxlSize);
679 appData.commentFont = InsertPxlSize(appData.commentFont, coordFontPxlSize);
680 appData.historyFont = InsertPxlSize(appData.historyFont, coordFontPxlSize);
681 appData.gameListFont = InsertPxlSize(appData.gameListFont, coordFontPxlSize);
687 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
688 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
689 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
690 appData.font = fontTable[MESSAGE_FONT][squareSize];
691 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
692 appData.coordFont = fontTable[COORD_FONT][squareSize];
695 appData.font = InsertPxlSize(appData.font, fontPxlSize);
696 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
697 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
698 fontSet = CreateFontSet(appData.font);
699 clockFontSet = CreateFontSet(appData.clockFont);
701 /* For the coordFont, use the 0th font of the fontset. */
702 XFontSet coordFontSet = CreateFontSet(appData.coordFont);
703 XFontStruct **font_struct_list;
704 XFontSetExtents *fontSize;
705 char **font_name_list;
706 XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
707 coordFontID = XLoadFont(xDisplay, font_name_list[0]);
708 coordFontStruct = XQueryFont(xDisplay, coordFontID);
709 fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
710 textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
713 appData.font = FindFont(appData.font, fontPxlSize);
714 appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
715 appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
716 clockFontID = XLoadFont(xDisplay, appData.clockFont);
717 clockFontStruct = XQueryFont(xDisplay, clockFontID);
718 coordFontID = XLoadFont(xDisplay, appData.coordFont);
719 coordFontStruct = XQueryFont(xDisplay, coordFontID);
720 // textHeight in !NLS mode!
722 countFontID = coordFontID; // [HGM] holdings
723 countFontStruct = coordFontStruct;
725 xdb = XtDatabase(xDisplay);
727 XrmPutLineResource(&xdb, "*international: True");
728 vTo.size = sizeof(XFontSet);
729 vTo.addr = (XtPointer) &fontSet;
730 XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
732 XrmPutStringResource(&xdb, "*font", appData.font);
743 case ArgInt: p = " N"; break;
744 case ArgString: p = " STR"; break;
745 case ArgBoolean: p = " TF"; break;
746 case ArgSettingsFilename:
747 case ArgBackupSettingsFile:
748 case ArgFilename: p = " FILE"; break;
749 case ArgX: p = " Nx"; break;
750 case ArgY: p = " Ny"; break;
751 case ArgAttribs: p = " TEXTCOL"; break;
752 case ArgColor: p = " COL"; break;
753 case ArgFont: p = " FONT"; break;
754 case ArgBoardSize: p = " SIZE"; break;
755 case ArgFloat: p = " FLOAT"; break;
760 case ArgCommSettings:
772 ArgDescriptor *q, *p = argDescriptors+5;
773 printf("\nXBoard accepts the following options:\n"
774 "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
775 " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
776 " SIZE = board-size spec(s)\n"
777 " Within parentheses are short forms, or options to set to true or false.\n"
778 " Persistent options (saved in the settings file) are marked with *)\n\n");
780 if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
781 snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
782 if(p->save) strcat(buf+len, "*");
783 for(q=p+1; q->argLoc == p->argLoc; q++) {
784 if(q->argName[0] == '-') continue;
785 strcat(buf+len, q == p+1 ? " (" : " ");
786 sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
788 if(q != p+1) strcat(buf+len, ")");
790 if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
793 if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
797 SlaveResize (Option *opt)
799 static int slaveW, slaveH, w, h;
802 gtk_widget_get_allocation(shells[DummyDlg], &a);
803 w = a.width; h = a.height;
804 gtk_widget_get_allocation(opt->handle, &a);
805 slaveW = w - opt->max; // [HGM] needed to set new shellWidget size when we resize board
806 slaveH = h - a.height + 13;
808 gtk_window_resize(GTK_WINDOW(shells[DummyDlg]), slaveW + opt->max, slaveH + opt->value);
812 LoadIconFile (gchar *svgFilename)
816 snprintf(buf, MSG_SIZ, "%s/%s" IMG, svgDir, svgFilename);
817 return gdk_pixbuf_new_from_file(buf, NULL);
821 static char clickedFile[MSG_SIZ];
825 StartNewXBoard(GtkosxApplication *app, gchar *path, gpointer user_data)
826 { // handler of OSX OpenFile signal, which sends us the filename of clicked file or first argument
827 if(suppress) { // we just started XBoard without arguments
828 strncpy(clickedFile, path, MSG_SIZ); // remember file name, but otherwise ignore
829 } else { // we are running something presumably useful
831 snprintf(buf, MSG_SIZ, "open -n -a \"xboard\" --args \"%s\"", path);
832 system(buf); // start new instance on this file
837 GtkosxApplication *theApp;
841 main (int argc, char **argv)
843 int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
844 int boardWidth, w, h; //, boardHeight;
846 int forceMono = False;
848 srandom(time(0)); // [HGM] book: make random truly random
850 setbuf(stdout, NULL);
851 setbuf(stderr, NULL);
854 if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
855 printf("%s version %s\n\n configure options: %s\n", PACKAGE_NAME, PACKAGE_VERSION, CONFIGURE_OPTIONS);
859 if(argc > 1 && !strcmp(argv[1], "--help" )) {
865 gtk_init (&argc, &argv);
867 { // prepare to catch OX OpenFile signal, which will tell us the clicked file
868 char *path = gtkosx_application_get_bundle_path();
869 theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
870 strncpy(dataDir, path, MSG_SIZ);
871 snprintf(masterSettings, MSG_SIZ, "%s/Contents/Resources/etc/xboard.conf", path);
872 snprintf(svgDir, MSG_SIZ, "%s/Contents/Resources/share/xboard/themes/default", path);
873 suppress = (argc == 1 || argc > 1 && argv[1][00] != '-'); // OSX sends signal even if name was already argv[1]!
874 g_signal_connect(theApp, "NSApplicationOpenFile", G_CALLBACK(StartNewXBoard), NULL);
875 // we must call application ready before we can get the signal,
876 // and supply a (dummy) menu bar before that, to avoid problems with dual apples in it
877 gtkosx_application_set_menu_bar(theApp, GTK_MENU_SHELL(gtk_menu_bar_new()));
878 gtkosx_application_ready(theApp);
879 if(argc == 1) { // called without args: OSX open-file signal might follow
880 static char *fakeArgv[3] = {NULL, clickedFile, NULL};
881 usleep(10000); // wait 10 msec (and hope this is long enough).
882 while(gtk_events_pending())
883 gtk_main_iteration(); // process all events that came in upto now
884 suppress = 0; // future open-file signals should start new instance
885 if(clickedFile[0]) { // we were sent an open-file signal with filename!
886 fakeArgv[0] = argv[0];
887 argc = 2; argv = fakeArgv; // fake that we were called as "xboard filename"
893 if(argc > 1 && !strcmp(argv[1], "--show-config")) { // [HGM] install: called to print config info
894 typedef struct {char *name, *value; } Config;
895 static Config configList[] = {
896 { "Datadir", DATADIR },
897 { "Sysconfdir", SYSCONFDIR },
902 for(i=0; configList[i].name; i++) {
903 if(argc > 2 && strcmp(argv[2], configList[i].name)) continue;
904 if(argc > 2) printf("%s", configList[i].value);
905 else printf("%-12s: %s\n", configList[i].name, configList[i].value);
910 /* set up keyboard accelerators group */
911 GtkAccelerators = gtk_accel_group_new();
913 programName = strrchr(argv[0], '/');
914 if (programName == NULL)
915 programName = argv[0];
920 // if (appData.debugMode) {
921 // fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
924 bindtextdomain(PACKAGE, LOCALEDIR);
925 bind_textdomain_codeset(PACKAGE, "UTF-8"); // needed when creating markup for the clocks
929 appData.boardSize = "";
930 InitAppData(ConvertToLine(argc, argv));
932 if (p == NULL) p = "/tmp";
933 i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
934 gameCopyFilename = (char*) malloc(i);
935 gamePasteFilename = (char*) malloc(i);
936 snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
937 snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
939 { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
940 static char buf[MSG_SIZ];
941 snprintf(buf, MSG_SIZ, appData.sysOpen, DATADIR);
942 ASSIGN(appData.sysOpen, buf); // expand %s in -openCommand to DATADIR (usefull for OS X configuring)
943 EscapeExpand(buf, appData.firstInitString);
944 appData.firstInitString = strdup(buf);
945 EscapeExpand(buf, appData.secondInitString);
946 appData.secondInitString = strdup(buf);
947 EscapeExpand(buf, appData.firstComputerString);
948 appData.firstComputerString = strdup(buf);
949 EscapeExpand(buf, appData.secondComputerString);
950 appData.secondComputerString = strdup(buf);
953 if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
956 if (chdir(chessDir) != 0) {
957 fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
963 if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
964 /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
965 if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL) {
966 printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
969 setbuf(debugFP, NULL);
973 if (appData.debugMode) {
974 fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
978 /* [HGM,HR] make sure board size is acceptable */
979 if(appData.NrFiles > BOARD_FILES ||
980 appData.NrRanks > BOARD_RANKS )
981 DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
984 /* This feature does not work; animation needs a rewrite */
985 appData.highlightDragging = FALSE;
989 gameInfo.variant = StringToVariant(appData.variant);
993 * determine size, based on supplied or remembered -size, or screen size
995 if (isdigit(appData.boardSize[0])) {
996 i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
997 &lineGap, &clockFontPxlSize, &coordFontPxlSize,
998 &fontPxlSize, &smallLayout, &tinyLayout);
1000 fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
1001 programName, appData.boardSize);
1005 squareSize = (squareSize*8 + BOARD_WIDTH/2)/BOARD_WIDTH; // scale height
1007 /* Find some defaults; use the nearest known size */
1008 SizeDefaults *szd, *nearest;
1009 int distance = 99999;
1010 nearest = szd = sizeDefaults;
1011 while (szd->name != NULL) {
1012 if (abs(szd->squareSize - squareSize) < distance) {
1014 distance = abs(szd->squareSize - squareSize);
1015 if (distance == 0) break;
1019 if (i < 2) lineGap = nearest->lineGap;
1020 if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
1021 if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
1022 if (i < 5) fontPxlSize = nearest->fontPxlSize;
1023 if (i < 6) smallLayout = nearest->smallLayout;
1024 if (i < 7) tinyLayout = nearest->tinyLayout;
1027 SizeDefaults *szd = sizeDefaults;
1028 if (*appData.boardSize == NULLCHAR) {
1029 GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow)); // TODO: this does not work, as no mainwindow yet
1030 guint screenwidth = gdk_screen_get_width(screen);
1031 guint screenheight = gdk_screen_get_height(screen);
1032 while (screenwidth < (szd->minScreenSize*BOARD_WIDTH + 4)/8 ||
1033 screenheight < (szd->minScreenSize*BOARD_HEIGHT + 4)/8) {
1036 if (szd->name == NULL) szd--;
1037 appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
1039 while (szd->name != NULL &&
1040 StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
1041 if (szd->name == NULL) {
1042 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
1043 programName, appData.boardSize);
1047 squareSize = szd->squareSize;
1048 lineGap = szd->lineGap;
1049 clockFontPxlSize = szd->clockFontPxlSize;
1050 coordFontPxlSize = szd->coordFontPxlSize;
1051 fontPxlSize = szd->fontPxlSize;
1052 smallLayout = szd->smallLayout;
1053 tinyLayout = szd->tinyLayout;
1054 // [HGM] font: use defaults from settings file if available and not overruled
1057 defaultLineGap = lineGap;
1058 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
1060 /* [HR] height treated separately (hacked) */
1061 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
1062 // boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1065 * Determine what fonts to use.
1067 InitializeFonts((2*clockFontPxlSize+1)/3, coordFontPxlSize, fontPxlSize);
1070 * Detect if there are not enough colors available and adapt.
1073 if (DefaultDepth(xDisplay, xScreen) <= 2) {
1074 appData.monoMode = True;
1078 forceMono = MakeColors();
1081 fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1083 appData.monoMode = True;
1086 ParseIcsTextColors();
1092 layoutName = "tinyLayout";
1093 } else if (smallLayout) {
1094 layoutName = "smallLayout";
1096 layoutName = "normalLayout";
1099 wpMain.width = -1; // prevent popup sizes window
1100 optList = BoardPopUp(squareSize, lineGap, (void*)
1110 InitDrawingHandle(optList + W_BOARD);
1111 shellWidget = shells[BoardWindow];
1112 currBoard = &optList[W_BOARD];
1113 boardWidget = optList[W_BOARD].handle;
1114 menuBarWidget = optList[W_MENU].handle;
1115 dropMenu = optList[W_DROP].handle;
1116 titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1118 formWidget = XtParent(boardWidget);
1119 XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1120 XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1121 XtGetValues(optList[W_WHITE].handle, args, 2);
1122 if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1123 XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1124 XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1125 XtGetValues(optList[W_PAUSE].handle, args, 2);
1129 // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1130 // not need to go into InitDrawingSizes().
1134 // add accelerators to main shell
1135 gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1138 * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1140 WhiteIcon = LoadIconFile("icon_white");
1141 BlackIcon = LoadIconFile("icon_black");
1142 SetClockIcon(0); // sets white icon
1146 * Create a cursor for the board widget.
1149 window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1150 XChangeWindowAttributes(xDisplay, xBoardWindow,
1151 CWCursor, &window_attributes);
1155 * Inhibit shell resizing.
1158 shellArgs[0].value = (XtArgVal) &w;
1159 shellArgs[1].value = (XtArgVal) &h;
1160 XtGetValues(shellWidget, shellArgs, 2);
1161 shellArgs[4].value = shellArgs[2].value = w;
1162 shellArgs[5].value = shellArgs[3].value = h;
1163 // XtSetValues(shellWidget, &shellArgs[2], 4);
1166 // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1167 // It wil only become known asynchronously, when we first write a string into it.
1168 // This will then change the clock widget height, which triggers resizing the top-level window
1169 // and a configure event. Only then can we know the total height of the top-level window,
1170 // and calculate the height we need. The clockKludge flag suppresses all resizing until
1171 // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1174 gtk_widget_get_allocation(shells[BoardWindow], &a);
1175 w = a.width; h = a.height;
1176 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1177 clockKludge = hc = a.height;
1178 gtk_widget_get_allocation(boardWidget, &a);
1179 marginW = w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1180 marginH = h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1186 if(appData.logoSize)
1187 { // locate and read user logo
1189 snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1190 ASSIGN(userLogo, buf);
1193 if (appData.animate || appData.animateDragging)
1196 g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1197 g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1199 /* [AS] Restore layout */
1200 if( wpMoveHistory.visible ) {
1204 if( wpEvalGraph.visible )
1209 if( wpEngineOutput.visible ) {
1210 EngineOutputPopUp();
1213 if( wpConsole.visible && appData.icsActive ) {
1218 gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1223 if (errorExitStatus == -1) {
1224 if (appData.icsActive) {
1225 /* We now wait until we see "login:" from the ICS before
1226 sending the logon script (problems with timestamp otherwise) */
1227 /*ICSInitScript();*/
1228 if (appData.icsInputBox) ICSInputBoxPopUp();
1232 signal(SIGWINCH, TermSizeSigHandler);
1234 signal(SIGINT, IntSigHandler);
1235 signal(SIGTERM, IntSigHandler);
1236 if (*appData.cmailGameName != NULLCHAR) {
1237 signal(SIGUSR1, CmailSigHandler);
1242 // XtSetKeyboardFocus(shellWidget, formWidget);
1244 XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1247 /* check for GTK events and process them */
1250 gtk_main_iteration();
1253 if (appData.debugMode) fclose(debugFP); // [DM] debug
1260 while(gtk_events_pending()) gtk_main_iteration();
1264 TermSizeSigHandler (int sig)
1270 IntSigHandler (int sig)
1276 CmailSigHandler (int sig)
1281 signal(SIGUSR1, SIG_IGN); /* suspend handler */
1283 /* Activate call-back function CmailSigHandlerCallBack() */
1284 OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1286 signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1290 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1293 ReloadCmailMsgEvent(TRUE); /* Reload cmail msg */
1295 /**** end signal code ****/
1298 #define Abs(n) ((n)<0 ? -(n) : (n))
1301 InsertPxlSize (char *pattern, int targetPxlSize)
1304 snprintf(buf, MSG_SIZ, pattern, targetPxlSize); // pattern is something like "Sans Bold %d"
1311 InsertPxlSize (char *pattern, int targetPxlSize)
1313 char *base_fnt_lst, strInt[12], *p, *q;
1314 int alternatives, i, len, strIntLen;
1317 * Replace the "*" (if present) in the pixel-size slot of each
1318 * alternative with the targetPxlSize.
1322 while ((p = strchr(p, ',')) != NULL) {
1326 snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1327 strIntLen = strlen(strInt);
1328 base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1332 while (alternatives--) {
1333 char *comma = strchr(p, ',');
1334 for (i=0; i<14; i++) {
1335 char *hyphen = strchr(p, '-');
1337 if (comma && hyphen > comma) break;
1338 len = hyphen + 1 - p;
1339 if (i == 7 && *p == '*' && len == 2) {
1341 memcpy(q, strInt, strIntLen);
1351 len = comma + 1 - p;
1358 return base_fnt_lst;
1364 CreateFontSet (char *base_fnt_lst)
1367 char **missing_list;
1371 fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1372 &missing_list, &missing_count, &def_string);
1373 if (appData.debugMode) {
1375 XFontStruct **font_struct_list;
1376 char **font_name_list;
1377 fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1379 fprintf(debugFP, " got list %s, locale %s\n",
1380 XBaseFontNameListOfFontSet(fntSet),
1381 XLocaleOfFontSet(fntSet));
1382 count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1383 for (i = 0; i < count; i++) {
1384 fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1387 for (i = 0; i < missing_count; i++) {
1388 fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1391 if (fntSet == NULL) {
1392 fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1398 #else // not ENABLE_NLS
1400 * Find a font that matches "pattern" that is as close as
1401 * possible to the targetPxlSize. Prefer fonts that are k
1402 * pixels smaller to fonts that are k pixels larger. The
1403 * pattern must be in the X Consortium standard format,
1404 * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1405 * The return value should be freed with XtFree when no
1410 FindFont (char *pattern, int targetPxlSize)
1412 char **fonts, *p, *best, *scalable, *scalableTail;
1413 int i, j, nfonts, minerr, err, pxlSize;
1415 fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1417 fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1418 programName, pattern);
1425 for (i=0; i<nfonts; i++) {
1428 if (*p != '-') continue;
1430 if (*p == NULLCHAR) break;
1431 if (*p++ == '-') j++;
1433 if (j < 7) continue;
1436 scalable = fonts[i];
1439 err = pxlSize - targetPxlSize;
1440 if (Abs(err) < Abs(minerr) ||
1441 (minerr > 0 && err < 0 && -err == minerr)) {
1447 if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1448 /* If the error is too big and there is a scalable font,
1449 use the scalable font. */
1450 int headlen = scalableTail - scalable;
1451 p = (char *) XtMalloc(strlen(scalable) + 10);
1452 while (isdigit(*scalableTail)) scalableTail++;
1453 sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1455 p = (char *) XtMalloc(strlen(best) + 2);
1456 safeStrCpy(p, best, strlen(best)+1 );
1458 if (appData.debugMode) {
1459 fprintf(debugFP, "resolved %s at pixel size %d\n to %s\n",
1460 pattern, targetPxlSize, p);
1462 XFreeFontNames(fonts);
1469 EnableNamedMenuItem (char *menuRef, int state)
1471 MenuItem *item = MenuNameToItem(menuRef);
1473 if(item && item->handle) gtk_widget_set_sensitive(item->handle, state);
1477 EnableButtonBar (int state)
1480 XtSetSensitive(optList[W_BUTTON].handle, state);
1486 SetMenuEnables (Enables *enab)
1488 while (enab->name != NULL) {
1489 EnableNamedMenuItem(enab->name, enab->value);
1494 gboolean KeyPressProc(window, eventkey, data)
1496 GdkEventKey *eventkey;
1500 MoveTypeInProc(eventkey); // pop up for typed in moves
1503 /* check for other key values */
1504 switch(eventkey->keyval) {
1516 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1517 { // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1519 if(*nprms == 0) return;
1520 item = MenuNameToItem(prms[0]);
1521 if(item) ((MenuProc *) item->proc) ();
1535 for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1536 entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1537 p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1538 dmEnables[i].piece);
1539 XtSetSensitive(entry, p != NULL || !appData.testLegality
1540 /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1541 && !appData.icsActive));
1543 while (p && *p++ == dmEnables[i].piece) count++;
1544 snprintf(label, sizeof(label), "%s %d", dmEnables[i].widget, count);
1546 XtSetArg(args[j], XtNlabel, label); j++;
1547 XtSetValues(entry, args, j);
1553 do_flash_delay (unsigned long msec)
1559 FlashDelay (int flash_delay)
1561 if(flash_delay) do_flash_delay(flash_delay);
1565 Fraction (int x, int start, int stop)
1567 double f = ((double) x - start)/(stop - start);
1568 if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1572 static WindowPlacement wpNew;
1575 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1577 int touch=0, fudge = 2, f = 2;
1578 GetActualPlacement(sh, wp);
1579 if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x) < fudge) touch = 1; else // right touch
1580 if(abs(wp->x + wp->width + 2*frameX + f - wpMain.x) < fudge) touch = 2; else // left touch
1581 if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1582 if(abs(wp->y + wp->height + frameX + frameY + f - wpMain.y) < fudge) touch = 4; // top touch
1583 //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);
1584 if(!touch ) return; // only windows that touch co-move
1585 if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1586 int heightInc = wpNew.height - wpMain.height;
1587 double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1588 double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1589 wp->y += fracTop * heightInc;
1590 heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1592 if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1594 wp->height += heightInc;
1595 } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1596 int widthInc = wpNew.width - wpMain.width;
1597 double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1598 double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1599 wp->y += fracLeft * widthInc;
1600 widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1602 if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1604 wp->width += widthInc;
1606 wp->x += wpNew.x - wpMain.x;
1607 wp->y += wpNew.y - wpMain.y;
1608 if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1609 if(touch == 3) wp->y += wpNew.height - wpMain.height;
1611 XtSetArg(args[j], XtNx, wp->x); j++;
1612 XtSetArg(args[j], XtNy, wp->y); j++;
1613 XtSetValues(sh, args, j);
1615 gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1616 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1617 gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1621 ReSize (WindowPlacement *wp)
1624 int sqx, sqy, w, h, hc, lg = lineGap;
1625 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1626 hc = a.height; // clock height can depend on single / double line clock text!
1627 if(clockKludge && hc != clockKludge) wp->height += hc - clockKludge, clockKludge = 0;
1628 wpMain.height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1629 if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1630 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1631 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1632 if(sqy < sqx) sqx = sqy;
1633 if(sqx < 20) return;
1634 if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1635 lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1636 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1637 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1638 if(sqy < sqx) sqx = sqy;
1640 if(sqx != squareSize) {
1641 squareSize = sqx; // adopt new square size
1642 CreatePNGPieces(); // make newly scaled pieces
1643 InitDrawingSizes(0, 0); // creates grid etc.
1644 } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1645 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1646 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1647 if(optList[W_BOARD].max > w) optList[W_BOARD].max = w;
1648 if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1651 static guint delayedDragTag = 0;
1660 // GetActualPlacement(shellWidget, &wpNew);
1661 if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1662 wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1663 busy = 0; return; // false alarm
1666 if(appData.useStickyWindows) {
1667 if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1668 if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1669 if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1670 if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1671 if(shellUp[ChatDlg]) CoDrag(shells[ChatDlg], &wpConsole);
1674 DrawPosition(True, NULL);
1675 if(delayedDragTag) g_source_remove(delayedDragTag);
1676 delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1683 //printf("old timr = %d\n", delayedDragTag);
1684 if(delayedDragTag) g_source_remove(delayedDragTag);
1685 delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1686 //printf("new timr = %d\n", delayedDragTag);
1690 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1692 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1694 wpNew.x = event->configure.x;
1695 wpNew.y = event->configure.y;
1696 wpNew.width = event->configure.width;
1697 wpNew.height = event->configure.height;
1698 DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1704 /* Disable all user input other than deleting the window */
1705 static int frozen = 0;
1711 /* Grab by a widget that doesn't accept input */
1712 gtk_grab_add(optList[W_MESSG].handle);
1716 /* Undo a FreezeUI */
1720 if (!frozen) return;
1721 gtk_grab_remove(optList[W_MESSG].handle);
1728 static int oldPausing = FALSE;
1729 static GameMode oldmode = (GameMode) -1;
1731 if (!boardWidget) return;
1733 if (pausing != oldPausing) {
1734 oldPausing = pausing;
1735 MarkMenuItem("Mode.Pause", pausing);
1737 if (appData.showButtonBar) {
1738 /* Always toggle, don't set. Previous code messes up when
1739 invoked while the button is pressed, as releasing it
1740 toggles the state again. */
1742 gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1743 gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1747 wname = ModeToWidgetName(oldmode);
1748 if (wname != NULL) {
1749 MarkMenuItem(wname, False);
1751 wname = ModeToWidgetName(gameMode);
1752 if (wname != NULL) {
1753 MarkMenuItem(wname, True);
1756 MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1758 /* Maybe all the enables should be handled here, not just this one */
1759 EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1761 DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1766 * Button/menu procedures
1769 void CopyFileToClipboard(gchar *filename)
1771 gchar *selection_tmp;
1775 FILE* f = fopen(filename, "r");
1778 if (f == NULL) return;
1782 selection_tmp = g_try_malloc(len + 1);
1783 if (selection_tmp == NULL) {
1784 printf("Malloc failed in CopyFileToClipboard\n");
1787 count = fread(selection_tmp, 1, len, f);
1790 g_free(selection_tmp);
1793 selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1795 // copy selection_tmp to clipboard
1796 GdkDisplay *gdisp = gdk_display_get_default();
1798 g_free(selection_tmp);
1801 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1802 gtk_clipboard_set_text(cb, selection_tmp, -1);
1803 g_free(selection_tmp);
1807 CopySomething (char *src)
1809 GdkDisplay *gdisp = gdk_display_get_default();
1811 if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1812 if (gdisp == NULL) return;
1813 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1814 gtk_clipboard_set_text(cb, src, -1);
1818 PastePositionProc ()
1820 GdkDisplay *gdisp = gdk_display_get_default();
1824 if (gdisp == NULL) return;
1825 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1826 fenstr = gtk_clipboard_wait_for_text(cb);
1827 if (fenstr==NULL) return; // nothing had been selected to copy
1828 EditPositionPasteFEN(fenstr);
1840 // get game from clipboard
1841 GdkDisplay *gdisp = gdk_display_get_default();
1842 if (gdisp == NULL) return;
1843 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1844 text = gtk_clipboard_wait_for_text(cb);
1845 if (text == NULL) return; // nothing to paste
1848 // write to temp file
1849 if (text == NULL || len == 0) {
1850 return; //nothing to paste
1852 f = fopen(gamePasteFilename, "w");
1854 DisplayError(_("Can't open temp file"), errno);
1857 fwrite(text, 1, len, f);
1861 LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1868 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1874 void MoveTypeInProc(eventkey)
1875 GdkEventKey *eventkey;
1879 // ingnore if ctrl, alt, or meta is pressed
1880 if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1884 buf[0]=eventkey->keyval;
1886 if (eventkey->keyval > 32 && eventkey->keyval < 256)
1887 ConsoleAutoPopUp (buf);
1892 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1894 if (!TempBackwardActive) {
1895 TempBackwardActive = True;
1901 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1903 /* Check to see if triggered by a key release event for a repeating key.
1904 * If so the next queued event will be a key press of the same key at the same time */
1905 if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1907 XPeekEvent(xDisplay, &next);
1908 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1909 next.xkey.keycode == event->xkey.keycode)
1913 TempBackwardActive = False;
1919 { // called from menu
1922 snprintf(buf, MSG_SIZ, "%s ./man.command", appData.sysOpen);
1925 system("xterm -e man xboard &");
1930 SetWindowTitle (char *text, char *title, char *icon)
1935 if (appData.titleInWindow) {
1937 XtSetArg(args[i], XtNlabel, text); i++;
1938 XtSetValues(titleWidget, args, i);
1941 XtSetArg(args[i], XtNiconName, (XtArgVal) icon); i++;
1942 XtSetArg(args[i], XtNtitle, (XtArgVal) title); i++;
1943 XtSetValues(shellWidget, args, i);
1944 XSync(xDisplay, False);
1946 if (appData.titleInWindow) {
1947 SetWidgetLabel(titleWidget, text);
1949 gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1954 DisplayIcsInteractionTitle (String message)
1957 if (oldICSInteractionTitle == NULL) {
1958 /* Magic to find the old window title, adapted from vim */
1959 char *wina = getenv("WINDOWID");
1961 Window win = (Window) atoi(wina);
1962 Window root, parent, *children;
1963 unsigned int nchildren;
1964 int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
1966 if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
1967 if (!XQueryTree(xDisplay, win, &root, &parent,
1968 &children, &nchildren)) break;
1969 if (children) XFree((void *)children);
1970 if (parent == root || parent == 0) break;
1973 XSetErrorHandler(oldHandler);
1975 if (oldICSInteractionTitle == NULL) {
1976 oldICSInteractionTitle = "xterm";
1979 printf("\033]0;%s\007", message);
1986 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
1988 GtkWidget *w = (GtkWidget *) opt->handle;
1995 strcpy(bgcolor, "black");
1996 strcpy(fgcolor, "white");
1998 strcpy(bgcolor, "white");
1999 strcpy(fgcolor, "black");
2002 appData.lowTimeWarning &&
2003 (timer / 1000) < appData.icsAlarmTime) {
2004 strcpy(fgcolor, appData.lowTimeWarningColor);
2007 gdk_color_parse( bgcolor, &col );
2008 gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
2010 if (appData.clockMode) {
2011 markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>", appData.clockFont,
2012 bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2013 // markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
2014 // bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2016 markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s </span>", appData.clockFont,
2017 bgcolor, fgcolor, color);
2018 // markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s </span>",
2019 // bgcolor, fgcolor, color);
2021 gtk_label_set_markup(GTK_LABEL(w), markup);
2025 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
2028 SetClockIcon (int color)
2030 GdkPixbuf *pm = *clockIcons[color];
2031 if (mainwindowIcon != pm) {
2032 mainwindowIcon = pm;
2033 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
2037 #define INPUT_SOURCE_BUF_SIZE 8192
2046 char buf[INPUT_SOURCE_BUF_SIZE];
2051 DoInputCallback(io, cond, data)
2056 /* read input from one of the input source (for example a chess program, ICS, etc).
2057 * and call a function that will handle the input
2064 /* All information (callback function, file descriptor, etc) is
2065 * saved in an InputSource structure
2067 InputSource *is = (InputSource *) data;
2069 if (is->lineByLine) {
2070 count = read(is->fd, is->unused,
2071 INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
2073 if(count == 0 && is->kind == CPReal && shells[ChatDlg]) { // [HGM] absence of terminal is no error if ICS Console present
2074 RemoveInputSource(is); // cease reading stdin
2075 stdoutClosed = TRUE; // suppress future output
2078 (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
2081 is->unused += count;
2083 /* break input into lines and call the callback function on each
2086 while (p < is->unused) {
2087 q = memchr(p, '\n', is->unused - p);
2088 if (q == NULL) break;
2090 (is->func)(is, is->closure, p, q - p, 0);
2093 /* remember not yet used part of the buffer */
2095 while (p < is->unused) {
2100 /* read maximum length of input buffer and send the whole buffer
2101 * to the callback function
2103 count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2108 (is->func)(is, is->closure, is->buf, count, error);
2110 return True; // Must return true or the watch will be removed
2113 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2120 GIOChannel *channel;
2121 ChildProc *cp = (ChildProc *) pr;
2123 is = (InputSource *) calloc(1, sizeof(InputSource));
2124 is->lineByLine = lineByLine;
2128 is->fd = fileno(stdin);
2130 is->kind = cp->kind;
2131 is->fd = cp->fdFrom;
2134 is->unused = is->buf;
2138 /* GTK-TODO: will this work on windows?*/
2140 channel = g_io_channel_unix_new(is->fd);
2141 g_io_channel_set_close_on_unref (channel, TRUE);
2142 is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2144 is->closure = closure;
2145 return (InputSourceRef) is;
2150 RemoveInputSource(isr)
2153 InputSource *is = (InputSource *) isr;
2155 if (is->sid == 0) return;
2156 g_source_remove(is->sid);
2163 static Boolean frameWaiting;
2166 FrameAlarm (int sig)
2168 frameWaiting = False;
2169 /* In case System-V style signals. Needed?? */
2170 signal(SIGALRM, FrameAlarm);
2174 FrameDelay (int time)
2176 struct itimerval delay;
2179 frameWaiting = True;
2180 signal(SIGALRM, FrameAlarm);
2181 delay.it_interval.tv_sec =
2182 delay.it_value.tv_sec = time / 1000;
2183 delay.it_interval.tv_usec =
2184 delay.it_value.tv_usec = (time % 1000) * 1000;
2185 setitimer(ITIMER_REAL, &delay, NULL);
2186 while (frameWaiting) pause();
2187 delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2188 delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2189 setitimer(ITIMER_REAL, &delay, NULL);
2196 FrameDelay (int time)
2199 XSync(xDisplay, False);
2201 // gtk_main_iteration_do(False);
2204 usleep(time * 1000);
2210 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2212 char buf[MSG_SIZ], *logoName = buf;
2213 if(appData.logo[n][0]) {
2214 logoName = appData.logo[n];
2215 } else if(appData.autoLogo) {
2216 if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2217 sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2218 } else if(appData.directory[n] && appData.directory[n][0]) {
2219 sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2223 { ASSIGN(cps->programLogo, logoName); }
2227 UpdateLogos (int displ)
2229 if(optList[W_WHITE-1].handle == NULL) return;
2230 LoadLogo(&first, 0, 0);
2231 LoadLogo(&second, 1, appData.icsActive);
2232 if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2236 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2247 GtkFileFilter *gtkfilter;
2248 GtkFileFilter *gtkfilter_all;
2250 char fileext[10] = "";
2251 char *result = NULL;
2254 /* make a copy of the filter string, so that strtok can work with it*/
2255 cp = strdup(filter);
2257 /* add filters for file extensions */
2258 gtkfilter = gtk_file_filter_new();
2259 gtkfilter_all = gtk_file_filter_new();
2261 /* one filter to show everything */
2262 gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2263 gtk_file_filter_set_name (gtkfilter_all, "All Files");
2265 /* add filter if present */
2266 result = strtok(cp, space);
2267 while( result != NULL ) {
2268 snprintf(fileext,10,"*%s",result);
2269 result = strtok( NULL, space );
2270 gtk_file_filter_add_pattern(gtkfilter, fileext);
2273 /* second filter to only show what's useful */
2274 gtk_file_filter_set_name (gtkfilter,filter);
2276 if (openMode[0] == 'r')
2278 dialog = gtk_file_chooser_dialog_new (label,
2280 GTK_FILE_CHOOSER_ACTION_OPEN,
2281 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2282 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2287 dialog = gtk_file_chooser_dialog_new (label,
2289 GTK_FILE_CHOOSER_ACTION_SAVE,
2290 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2291 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2293 /* add filename suggestions */
2294 if (strlen(def) > 0 )
2295 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2297 //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2301 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2302 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2303 /* activate filter */
2304 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2306 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2311 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2314 f = fopen(filename, openMode);
2317 DisplayError(_("Failed to open file"), errno);
2321 /* TODO add indec */
2323 ASSIGN(*name, filename);
2324 ScheduleDelayedEvent(DelayedLoad, 50);
2329 gtk_widget_destroy (dialog);