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 # define SYNC_MENUBAR gtkosx_application_sync_menubar(theApp)
184 char dataDir[MSG_SIZ]; // for expanding ~~
185 char masterSettings[MSG_SIZ];
189 # define SYNC_MENUBAR
196 #define usleep(t) _sleep2(((t)+500)/1000)
200 # define _(s) gettext (s)
201 # define N_(s) gettext_noop (s)
207 int main P((int argc, char **argv));
208 RETSIGTYPE CmailSigHandler P((int sig));
209 RETSIGTYPE IntSigHandler P((int sig));
210 RETSIGTYPE TermSizeSigHandler P((int sig));
211 char *InsertPxlSize P((char *pattern, int targetPxlSize));
213 XFontSet CreateFontSet P((char *base_fnt_lst));
215 char *FindFont P((char *pattern, int targetPxlSize));
217 void DelayedDrag P((void));
218 void ICSInputBoxPopUp P((void));
219 void MoveTypeInProc P((GdkEventKey *eventkey));
220 gboolean KeyPressProc P((GtkWindow *window, GdkEventKey *eventkey, gpointer data));
221 Boolean TempBackwardActive = False;
222 void DisplayMove P((int moveNumber));
223 void update_ics_width P(());
224 int CopyMemoProc P(());
225 static gboolean EventProc P((GtkWidget *widget, GdkEvent *event, gpointer g));
229 XFontSet fontSet, clockFontSet;
232 XFontStruct *clockFontStruct;
234 Font coordFontID, countFontID;
235 XFontStruct *coordFontStruct, *countFontStruct;
237 void *shellWidget, *formWidget, *boardWidget, *titleWidget, *dropMenu, *menuBarWidget;
238 GtkWidget *mainwindow;
240 Option *optList; // contains all widgets of main window
243 char installDir[] = "."; // [HGM] UCI: needed for UCI; probably needs run-time initializtion
246 static GdkPixbuf *mainwindowIcon=NULL;
247 static GdkPixbuf *WhiteIcon=NULL;
248 static GdkPixbuf *BlackIcon=NULL;
250 /* key board accelerators */
251 GtkAccelGroup *GtkAccelerators;
253 typedef unsigned int BoardSize;
255 Boolean chessProgram;
257 int minX, minY; // [HGM] placement: volatile limits on upper-left corner
258 int smallLayout = 0, tinyLayout = 0,
259 marginW, marginH, // [HGM] for run-time resizing
260 fromX = -1, fromY = -1, toX, toY, commentUp = False,
261 errorExitStatus = -1, defaultLineGap;
263 Dimension textHeight;
265 char *chessDir, *programName, *programVersion;
266 Boolean alwaysOnTop = False;
267 char *icsTextMenuString;
269 char *firstChessProgramNames;
270 char *secondChessProgramNames;
272 WindowPlacement wpMain;
273 WindowPlacement wpConsole;
274 WindowPlacement wpComment;
275 WindowPlacement wpMoveHistory;
276 WindowPlacement wpEvalGraph;
277 WindowPlacement wpEngineOutput;
278 WindowPlacement wpGameList;
279 WindowPlacement wpTags;
280 WindowPlacement wpDualBoard;
282 /* This magic number is the number of intermediate frames used
283 in each half of the animation. For short moves it's reduced
284 by 1. The total number of frames will be factor * 2 + 1. */
287 SizeDefaults sizeDefaults[] = SIZE_DEFAULTS;
294 DropMenuEnables dmEnables[] = {
303 XtResource clientResources[] = {
304 { "flashCount", "flashCount", XtRInt, sizeof(int),
305 XtOffset(AppDataPtr, flashCount), XtRImmediate,
306 (XtPointer) FLASH_COUNT },
310 /* keyboard shortcuts not yet transistioned int menuitem @ menu.c */
311 char globalTranslations[] =
312 ":Ctrl<Key>Down: LoadSelectedProc(3) \n \
313 :Ctrl<Key>Up: LoadSelectedProc(-3) \n \
314 :<KeyDown>Return: TempBackwardProc() \n \
315 :<KeyUp>Return: TempForwardProc() \n";
317 char ICSInputTranslations[] =
318 "<Key>Up: UpKeyProc() \n "
319 "<Key>Down: DownKeyProc() \n "
320 "<Key>Return: EnterKeyProc() \n";
322 // [HGM] vari: another hideous kludge: call extend-end first so we can be sure select-start works,
323 // as the widget is destroyed before the up-click can call extend-end
324 char commentTranslations[] = "<Btn3Down>: extend-end() select-start() CommentClick() \n";
327 String xboardResources[] = {
328 "*Error*translations: #override\\n <Key>Return: ErrorPopDown()",
336 gtk_window_present(GTK_WINDOW(shells[BoardWindow]));
339 //---------------------------------------------------------------------------------------------------------
340 // some symbol definitions to provide the proper (= XBoard) context for the code in args.h
343 #define CW_USEDEFAULT (1<<31)
344 #define ICS_TEXT_MENU_SIZE 90
345 #define DEBUG_FILE "xboard.debug"
346 #define SetCurrentDirectory chdir
347 #define GetCurrentDirectory(SIZE, NAME) getcwd(NAME, SIZE)
351 // The option definition and parsing code common to XBoard and WinBoard is collected in this file
354 // front-end part of option handling
356 // [HGM] This platform-dependent table provides the location for storing the color info
357 extern char *crWhite, * crBlack;
361 &appData.whitePieceColor,
362 &appData.blackPieceColor,
363 &appData.lightSquareColor,
364 &appData.darkSquareColor,
365 &appData.highlightSquareColor,
366 &appData.premoveHighlightColor,
367 &appData.lowTimeWarningColor,
378 // [HGM] font: keep a font for each square size, even non-stndard ones
381 Boolean fontIsSet[NUM_FONTS], fontValid[NUM_FONTS][MAX_SIZE];
382 char *fontTable[NUM_FONTS][MAX_SIZE];
385 ParseFont (char *name, int number)
386 { // in XBoard, only 2 of the fonts are currently implemented, and we just copy their name
388 if(sscanf(name, "size%d:", &size)) {
389 // [HGM] font: font is meant for specific boardSize (likely from settings file);
390 // defer processing it until we know if it matches our board size
391 if(!strstr(name, "-*-") && // ignore X-fonts
392 size >= 0 && size<MAX_SIZE) { // for now, fixed limit
393 fontTable[number][size] = strdup(strchr(name, ':')+1);
394 fontValid[number][size] = True;
399 case 0: // CLOCK_FONT
400 appData.clockFont = strdup(name);
402 case 1: // MESSAGE_FONT
403 appData.font = strdup(name);
405 case 2: // COORD_FONT
406 appData.coordFont = strdup(name);
409 appData.icsFont = strdup(name);
412 appData.tagsFont = strdup(name);
415 appData.commentFont = strdup(name);
417 case MOVEHISTORY_FONT:
418 appData.historyFont = strdup(name);
421 appData.gameListFont = strdup(name);
426 fontIsSet[number] = True; // [HGM] font: indicate a font was specified (not from settings file)
431 { // only 2 fonts currently
432 appData.clockFont = strdup(CLOCK_FONT_NAME);
433 appData.coordFont = strdup(COORD_FONT_NAME);
434 appData.font = strdup(DEFAULT_FONT_NAME);
435 appData.icsFont = strdup(CONSOLE_FONT_NAME);
436 appData.tagsFont = strdup(TAGS_FONT_NAME);
437 appData.commentFont = strdup(COMMENT_FONT_NAME);
438 appData.historyFont = strdup(HISTORY_FONT_NAME);
439 appData.gameListFont = strdup(GAMELIST_FONT_NAME);
444 { // no-op, until we identify the code for this already in XBoard and move it here
448 ParseColor (int n, char *name)
449 { // in XBoard, just copy the color-name string
450 if(colorVariable[n] && *name == '#') *(char**)colorVariable[n] = strdup(name);
456 return *(char**)colorVariable[n];
460 ParseTextAttribs (ColorClass cc, char *s)
462 (&appData.colorShout)[cc] = strdup(s);
466 ParseBoardSize (void *addr, char *name)
468 appData.boardSize = strdup(name);
473 { // In XBoard the sound-playing program takes care of obtaining the actual sound
477 SetCommPortDefaults ()
478 { // for now, this is a no-op, as the corresponding option does not exist in XBoard
481 // [HGM] args: these three cases taken out to stay in front-end
483 SaveFontArg (FILE *f, ArgDescriptor *ad)
486 int i, n = (int)(intptr_t)ad->argLoc;
488 case 0: // CLOCK_FONT
489 name = appData.clockFont;
491 case 1: // MESSAGE_FONT
494 case 2: // COORD_FONT
495 name = appData.coordFont;
498 name = appData.icsFont;
501 name = appData.tagsFont;
504 name = appData.commentFont;
506 case MOVEHISTORY_FONT:
507 name = appData.historyFont;
510 name = appData.gameListFont;
515 for(i=0; i<NUM_SIZES; i++) // [HGM] font: current font becomes standard for current size
516 if(sizeDefaults[i].squareSize == squareSize) { // only for standard sizes!
517 fontTable[n][squareSize] = strdup(name);
518 fontValid[n][squareSize] = True;
521 for(i=0; i<MAX_SIZE; i++) if(fontValid[n][i]) // [HGM] font: store all standard fonts
522 fprintf(f, OPTCHAR "%s" SEPCHAR "\"size%d:%s\"\n", ad->argName, i, fontTable[n][i]);
527 { // nothing to do, as the sounds are at all times represented by their text-string names already
531 SaveAttribsArg (FILE *f, ArgDescriptor *ad)
532 { // here the "argLoc" defines a table index. It could have contained the 'ta' pointer itself, though
533 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, (&appData.colorShout)[(int)(intptr_t)ad->argLoc]);
537 SaveColor (FILE *f, ArgDescriptor *ad)
538 { // in WinBoard the color is an int and has to be converted to text. In X it would be a string already?
539 if(colorVariable[(int)(intptr_t)ad->argLoc])
540 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", ad->argName, *(char**)colorVariable[(int)(intptr_t)ad->argLoc]);
544 SaveBoardSize (FILE *f, char *name, void *addr)
545 { // wrapper to shield back-end from BoardSize & sizeInfo
546 fprintf(f, OPTCHAR "%s" SEPCHAR "%s\n", name, appData.boardSize);
550 ParseCommPortSettings (char *s)
551 { // no such option in XBoard (yet)
557 GetActualPlacement (GtkWidget *shell, WindowPlacement *wp)
561 gtk_widget_get_allocation(shell, &a);
562 gtk_window_get_position(GTK_WINDOW(shell), &a.x, &a.y);
566 wp->height = a.height;
567 //printf("placement: (%d,%d) %dx%d\n", a.x, a.y, a.width, a.height);
568 frameX = 3; frameY = 3; // remember to decide if windows touch
572 GetPlacement (DialogClass dlg, WindowPlacement *wp)
573 { // wrapper to shield back-end from widget type
574 if(shellUp[dlg]) GetActualPlacement(shells[dlg], wp);
579 { // wrapper to shield use of window handles from back-end (make addressible by number?)
580 // In XBoard this will have to wait until awareness of window parameters is implemented
581 GetActualPlacement(shellWidget, &wpMain);
582 if(shellUp[EngOutDlg]) GetActualPlacement(shells[EngOutDlg], &wpEngineOutput);
583 if(shellUp[HistoryDlg]) GetActualPlacement(shells[HistoryDlg], &wpMoveHistory);
584 if(shellUp[EvalGraphDlg]) GetActualPlacement(shells[EvalGraphDlg], &wpEvalGraph);
585 if(shellUp[GameListDlg]) GetActualPlacement(shells[GameListDlg], &wpGameList);
586 if(shellUp[CommentDlg]) GetActualPlacement(shells[CommentDlg], &wpComment);
587 if(shellUp[TagsDlg]) GetActualPlacement(shells[TagsDlg], &wpTags);
588 GetPlacement(ChatDlg, &wpConsole); if(appData.icsActive) wpConsole.visible = shellUp[ChatDlg];
592 PrintCommPortSettings (FILE *f, char *name)
593 { // This option does not exist in XBoard
597 EnsureOnScreen (int *x, int *y, int minX, int minY)
604 { // [HGM] args: allows testing if main window is realized from back-end
605 return DialogExists(BoardWindow);
609 PopUpStartupDialog ()
610 { // start menu not implemented in XBoard
614 ConvertToLine (int argc, char **argv)
616 static char line[128*1024], buf[1024];
620 for(i=1; i<argc; i++)
622 if( (strchr(argv[i], ' ') || strchr(argv[i], '\n') ||strchr(argv[i], '\t') || argv[i][0] == NULLCHAR)
623 && argv[i][0] != '{' )
624 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "{%s} ", argv[i]);
626 snprintf(buf, sizeof(buf)/sizeof(buf[0]), "%s ", argv[i]);
627 strncat(line, buf, 128*1024 - strlen(line) - 1 );
630 line[strlen(line)-1] = NULLCHAR;
634 //--------------------------------------------------------------------------------------------
639 ResizeBoardWindow (int w, int h, int inhibit)
642 // if(clockKludge) return; // ignore as long as clock does not have final height
643 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
644 w += marginW + 1; // [HGM] not sure why the +1 is (sometimes) needed...
645 h += marginH + a.height + 1;
646 gtk_window_resize(GTK_WINDOW(shellWidget), w, h);
651 { // dummy, as the GTK code does not make colors in advance
656 InitializeFonts (int clockFontPxlSize, int coordFontPxlSize, int fontPxlSize)
657 { // determine what fonts to use, and create them
659 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
660 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
661 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
662 appData.font = fontTable[MESSAGE_FONT][squareSize];
663 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
664 appData.coordFont = fontTable[COORD_FONT][squareSize];
665 if(!fontIsSet[CONSOLE_FONT] && fontValid[CONSOLE_FONT][squareSize])
666 appData.icsFont = fontTable[CONSOLE_FONT][squareSize];
667 if(!fontIsSet[EDITTAGS_FONT] && fontValid[EDITTAGS_FONT][squareSize])
668 appData.tagsFont = fontTable[EDITTAGS_FONT][squareSize];
669 if(!fontIsSet[COMMENT_FONT] && fontValid[COMMENT_FONT][squareSize])
670 appData.commentFont = fontTable[COMMENT_FONT][squareSize];
671 if(!fontIsSet[MOVEHISTORY_FONT] && fontValid[MOVEHISTORY_FONT][squareSize])
672 appData.historyFont = fontTable[MOVEHISTORY_FONT][squareSize];
673 if(!fontIsSet[GAMELIST_FONT] && fontValid[GAMELIST_FONT][squareSize])
674 appData.gameListFont = fontTable[GAMELIST_FONT][squareSize];
676 appData.font = InsertPxlSize(appData.font, coordFontPxlSize);
677 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
678 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
679 appData.icsFont = InsertPxlSize(appData.icsFont, coordFontPxlSize);
680 appData.tagsFont = InsertPxlSize(appData.tagsFont, coordFontPxlSize);
681 appData.commentFont = InsertPxlSize(appData.commentFont, coordFontPxlSize);
682 appData.historyFont = InsertPxlSize(appData.historyFont, coordFontPxlSize);
683 appData.gameListFont = InsertPxlSize(appData.gameListFont, coordFontPxlSize);
689 if(!fontIsSet[CLOCK_FONT] && fontValid[CLOCK_FONT][squareSize])
690 appData.clockFont = fontTable[CLOCK_FONT][squareSize];
691 if(!fontIsSet[MESSAGE_FONT] && fontValid[MESSAGE_FONT][squareSize])
692 appData.font = fontTable[MESSAGE_FONT][squareSize];
693 if(!fontIsSet[COORD_FONT] && fontValid[COORD_FONT][squareSize])
694 appData.coordFont = fontTable[COORD_FONT][squareSize];
697 appData.font = InsertPxlSize(appData.font, fontPxlSize);
698 appData.clockFont = InsertPxlSize(appData.clockFont, clockFontPxlSize);
699 appData.coordFont = InsertPxlSize(appData.coordFont, coordFontPxlSize);
700 fontSet = CreateFontSet(appData.font);
701 clockFontSet = CreateFontSet(appData.clockFont);
703 /* For the coordFont, use the 0th font of the fontset. */
704 XFontSet coordFontSet = CreateFontSet(appData.coordFont);
705 XFontStruct **font_struct_list;
706 XFontSetExtents *fontSize;
707 char **font_name_list;
708 XFontsOfFontSet(coordFontSet, &font_struct_list, &font_name_list);
709 coordFontID = XLoadFont(xDisplay, font_name_list[0]);
710 coordFontStruct = XQueryFont(xDisplay, coordFontID);
711 fontSize = XExtentsOfFontSet(fontSet); // [HGM] figure out how much vertical space font takes
712 textHeight = fontSize->max_logical_extent.height + 5; // add borderWidth
715 appData.font = FindFont(appData.font, fontPxlSize);
716 appData.clockFont = FindFont(appData.clockFont, clockFontPxlSize);
717 appData.coordFont = FindFont(appData.coordFont, coordFontPxlSize);
718 clockFontID = XLoadFont(xDisplay, appData.clockFont);
719 clockFontStruct = XQueryFont(xDisplay, clockFontID);
720 coordFontID = XLoadFont(xDisplay, appData.coordFont);
721 coordFontStruct = XQueryFont(xDisplay, coordFontID);
722 // textHeight in !NLS mode!
724 countFontID = coordFontID; // [HGM] holdings
725 countFontStruct = coordFontStruct;
727 xdb = XtDatabase(xDisplay);
729 XrmPutLineResource(&xdb, "*international: True");
730 vTo.size = sizeof(XFontSet);
731 vTo.addr = (XtPointer) &fontSet;
732 XrmPutResource(&xdb, "*fontSet", XtRFontSet, &vTo);
734 XrmPutStringResource(&xdb, "*font", appData.font);
745 case ArgInt: p = " N"; break;
746 case ArgString: p = " STR"; break;
747 case ArgBoolean: p = " TF"; break;
748 case ArgSettingsFilename:
749 case ArgBackupSettingsFile:
750 case ArgFilename: p = " FILE"; break;
751 case ArgX: p = " Nx"; break;
752 case ArgY: p = " Ny"; break;
753 case ArgAttribs: p = " TEXTCOL"; break;
754 case ArgColor: p = " COL"; break;
755 case ArgFont: p = " FONT"; break;
756 case ArgBoardSize: p = " SIZE"; break;
757 case ArgFloat: p = " FLOAT"; break;
762 case ArgCommSettings:
774 ArgDescriptor *q, *p = argDescriptors+5;
775 printf("\nXBoard accepts the following options:\n"
776 "(N = integer, TF = true or false, STR = text string, FILE = filename,\n"
777 " Nx, Ny = relative coordinates, COL = color, FONT = X-font spec,\n"
778 " SIZE = board-size spec(s)\n"
779 " Within parentheses are short forms, or options to set to true or false.\n"
780 " Persistent options (saved in the settings file) are marked with *)\n\n");
782 if(p->argType == ArgCommSettings) { p++; continue; } // XBoard has no comm port
783 snprintf(buf+len, MSG_SIZ, "-%s%s", p->argName, PrintArg(p->argType));
784 if(p->save) strcat(buf+len, "*");
785 for(q=p+1; q->argLoc == p->argLoc; q++) {
786 if(q->argName[0] == '-') continue;
787 strcat(buf+len, q == p+1 ? " (" : " ");
788 sprintf(buf+strlen(buf), "-%s%s", q->argName, PrintArg(q->argType));
790 if(q != p+1) strcat(buf+len, ")");
792 if(len > 39) len = 0, printf("%s\n", buf); else while(len < 39) buf[len++] = ' ';
795 if(len) buf[len] = NULLCHAR, printf("%s\n", buf);
799 SlaveResize (Option *opt)
801 static int slaveW, slaveH, w, h;
804 gtk_widget_get_allocation(shells[DummyDlg], &a);
805 w = a.width; h = a.height;
806 gtk_widget_get_allocation(opt->handle, &a);
807 slaveW = w - opt->max; // [HGM] needed to set new shellWidget size when we resize board
808 slaveH = h - a.height + 13;
810 gtk_window_resize(GTK_WINDOW(shells[DummyDlg]), slaveW + opt->max, slaveH + opt->value);
814 LoadIconFile (gchar *svgFilename)
818 snprintf(buf, MSG_SIZ, "%s/%s" IMG, svgDir, svgFilename);
819 return gdk_pixbuf_new_from_file(buf, NULL);
823 static char clickedFile[MSG_SIZ];
827 StartNewXBoard(GtkosxApplication *app, gchar *path, gpointer user_data)
828 { // handler of OSX OpenFile signal, which sends us the filename of clicked file or first argument
829 if(suppress) { // we just started XBoard without arguments
830 strncpy(clickedFile, path, MSG_SIZ); // remember file name, but otherwise ignore
831 } else { // we are running something presumably useful
833 snprintf(buf, MSG_SIZ, "open -n -a \"xboard\" --args \"%s\"", path);
834 system(buf); // start new instance on this file
839 GtkosxApplication *theApp;
843 main (int argc, char **argv)
845 int i, clockFontPxlSize, coordFontPxlSize, fontPxlSize;
846 int boardWidth, w, h; //, boardHeight;
848 int forceMono = False;
850 srandom(time(0)); // [HGM] book: make random truly random
852 setbuf(stdout, NULL);
853 setbuf(stderr, NULL);
856 if(argc > 1 && (!strcmp(argv[1], "-v" ) || !strcmp(argv[1], "--version" ))) {
857 printf("%s version %s\n\n configure options: %s\n", PACKAGE_NAME, PACKAGE_VERSION, CONFIGURE_OPTIONS);
861 if(argc > 1 && !strcmp(argv[1], "--help" )) {
867 gtk_init (&argc, &argv);
869 { // prepare to catch OX OpenFile signal, which will tell us the clicked file
870 char *path = gtkosx_application_get_bundle_path();
871 theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
872 strncpy(dataDir, path, MSG_SIZ);
873 snprintf(masterSettings, MSG_SIZ, "%s/Contents/Resources/etc/xboard.conf", path);
874 snprintf(svgDir, MSG_SIZ, "%s/Contents/Resources/share/xboard/themes/default", path);
875 suppress = (argc == 1 || argc > 1 && argv[1][00] != '-'); // OSX sends signal even if name was already argv[1]!
876 g_signal_connect(theApp, "NSApplicationOpenFile", G_CALLBACK(StartNewXBoard), NULL);
877 // we must call application ready before we can get the signal,
878 // and supply a (dummy) menu bar before that, to avoid problems with dual apples in it
879 gtkosx_application_set_menu_bar(theApp, GTK_MENU_SHELL(gtk_menu_bar_new()));
880 gtkosx_application_ready(theApp);
881 if(argc == 1) { // called without args: OSX open-file signal might follow
882 static char *fakeArgv[3] = {NULL, clickedFile, NULL};
883 usleep(10000); // wait 10 msec (and hope this is long enough).
884 while(gtk_events_pending())
885 gtk_main_iteration(); // process all events that came in upto now
886 suppress = 0; // future open-file signals should start new instance
887 if(clickedFile[0]) { // we were sent an open-file signal with filename!
888 fakeArgv[0] = argv[0];
889 argc = 2; argv = fakeArgv; // fake that we were called as "xboard filename"
895 if(argc > 1 && !strcmp(argv[1], "--show-config")) { // [HGM] install: called to print config info
896 typedef struct {char *name, *value; } Config;
897 static Config configList[] = {
898 { "Datadir", DATADIR },
899 { "Sysconfdir", SYSCONFDIR },
904 for(i=0; configList[i].name; i++) {
905 if(argc > 2 && strcmp(argv[2], configList[i].name)) continue;
906 if(argc > 2) printf("%s", configList[i].value);
907 else printf("%-12s: %s\n", configList[i].name, configList[i].value);
912 /* set up keyboard accelerators group */
913 GtkAccelerators = gtk_accel_group_new();
915 programName = strrchr(argv[0], '/');
916 if (programName == NULL)
917 programName = argv[0];
922 // if (appData.debugMode) {
923 // fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
926 bindtextdomain(PACKAGE, LOCALEDIR);
927 bind_textdomain_codeset(PACKAGE, "UTF-8"); // needed when creating markup for the clocks
931 appData.boardSize = "";
932 InitAppData(ConvertToLine(argc, argv));
934 if (p == NULL) p = "/tmp";
935 i = strlen(p) + strlen("/.xboardXXXXXx.pgn") + 1;
936 gameCopyFilename = (char*) malloc(i);
937 gamePasteFilename = (char*) malloc(i);
938 snprintf(gameCopyFilename,i, "%s/.xboard%05uc.pgn", p, getpid());
939 snprintf(gamePasteFilename,i, "%s/.xboard%05up.pgn", p, getpid());
941 { // [HGM] initstring: kludge to fix bad bug. expand '\n' characters in init string and computer string.
942 static char buf[MSG_SIZ];
943 snprintf(buf, MSG_SIZ, appData.sysOpen, DATADIR);
944 ASSIGN(appData.sysOpen, buf); // expand %s in -openCommand to DATADIR (usefull for OS X configuring)
945 EscapeExpand(buf, appData.firstInitString);
946 appData.firstInitString = strdup(buf);
947 EscapeExpand(buf, appData.secondInitString);
948 appData.secondInitString = strdup(buf);
949 EscapeExpand(buf, appData.firstComputerString);
950 appData.firstComputerString = strdup(buf);
951 EscapeExpand(buf, appData.secondComputerString);
952 appData.secondComputerString = strdup(buf);
955 if ((chessDir = (char *) getenv("CHESSDIR")) == NULL) {
958 if (chdir(chessDir) != 0) {
959 fprintf(stderr, _("%s: can't cd to CHESSDIR: "), programName);
965 if (appData.debugMode && appData.nameOfDebugFile && strcmp(appData.nameOfDebugFile, "stderr")) {
966 /* [DM] debug info to file [HGM] make the filename a command-line option, and allow it to remain stderr */
967 if ((debugFP = fopen(appData.nameOfDebugFile, "w")) == NULL) {
968 printf(_("Failed to open file '%s'\n"), appData.nameOfDebugFile);
971 setbuf(debugFP, NULL);
975 if (appData.debugMode) {
976 fprintf(debugFP, "locale = %s\n", setlocale(LC_ALL, NULL));
980 /* [HGM,HR] make sure board size is acceptable */
981 if(appData.NrFiles > BOARD_FILES ||
982 appData.NrRanks > BOARD_RANKS )
983 DisplayFatalError(_("Recompile with larger BOARD_RANKS or BOARD_FILES to support this size"), 0, 2);
986 /* This feature does not work; animation needs a rewrite */
987 appData.highlightDragging = FALSE;
991 gameInfo.variant = StringToVariant(appData.variant);
995 * determine size, based on supplied or remembered -size, or screen size
997 if (isdigit(appData.boardSize[0])) {
998 i = sscanf(appData.boardSize, "%d,%d,%d,%d,%d,%d,%d", &squareSize,
999 &lineGap, &clockFontPxlSize, &coordFontPxlSize,
1000 &fontPxlSize, &smallLayout, &tinyLayout);
1002 fprintf(stderr, _("%s: bad boardSize syntax %s\n"),
1003 programName, appData.boardSize);
1007 squareSize = (squareSize*8 + BOARD_WIDTH/2)/BOARD_WIDTH; // scale height
1009 /* Find some defaults; use the nearest known size */
1010 SizeDefaults *szd, *nearest;
1011 int distance = 99999;
1012 nearest = szd = sizeDefaults;
1013 while (szd->name != NULL) {
1014 if (abs(szd->squareSize - squareSize) < distance) {
1016 distance = abs(szd->squareSize - squareSize);
1017 if (distance == 0) break;
1021 if (i < 2) lineGap = nearest->lineGap;
1022 if (i < 3) clockFontPxlSize = nearest->clockFontPxlSize;
1023 if (i < 4) coordFontPxlSize = nearest->coordFontPxlSize;
1024 if (i < 5) fontPxlSize = nearest->fontPxlSize;
1025 if (i < 6) smallLayout = nearest->smallLayout;
1026 if (i < 7) tinyLayout = nearest->tinyLayout;
1029 SizeDefaults *szd = sizeDefaults;
1030 if (*appData.boardSize == NULLCHAR) {
1031 GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(mainwindow)); // TODO: this does not work, as no mainwindow yet
1032 guint screenwidth = gdk_screen_get_width(screen);
1033 guint screenheight = gdk_screen_get_height(screen);
1034 while (screenwidth < (szd->minScreenSize*BOARD_WIDTH + 4)/8 ||
1035 screenheight < (szd->minScreenSize*BOARD_HEIGHT + 4)/8) {
1038 if (szd->name == NULL) szd--;
1039 appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
1041 while (szd->name != NULL &&
1042 StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
1043 if (szd->name == NULL) {
1044 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
1045 programName, appData.boardSize);
1049 squareSize = szd->squareSize;
1050 lineGap = szd->lineGap;
1051 clockFontPxlSize = szd->clockFontPxlSize;
1052 coordFontPxlSize = szd->coordFontPxlSize;
1053 fontPxlSize = szd->fontPxlSize;
1054 smallLayout = szd->smallLayout;
1055 tinyLayout = szd->tinyLayout;
1056 // [HGM] font: use defaults from settings file if available and not overruled
1059 defaultLineGap = lineGap;
1060 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
1062 /* [HR] height treated separately (hacked) */
1063 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
1064 // boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1067 * Determine what fonts to use.
1069 InitializeFonts((2*clockFontPxlSize+1)/3, coordFontPxlSize, fontPxlSize);
1072 * Detect if there are not enough colors available and adapt.
1075 if (DefaultDepth(xDisplay, xScreen) <= 2) {
1076 appData.monoMode = True;
1080 forceMono = MakeColors();
1083 fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1085 appData.monoMode = True;
1088 ParseIcsTextColors();
1094 layoutName = "tinyLayout";
1095 } else if (smallLayout) {
1096 layoutName = "smallLayout";
1098 layoutName = "normalLayout";
1101 wpMain.width = -1; // prevent popup sizes window
1102 optList = BoardPopUp(squareSize, lineGap, (void*)
1112 InitDrawingHandle(optList + W_BOARD);
1113 shellWidget = shells[BoardWindow];
1114 currBoard = &optList[W_BOARD];
1115 boardWidget = optList[W_BOARD].handle;
1116 menuBarWidget = optList[W_MENU].handle;
1117 dropMenu = optList[W_DROP].handle;
1118 titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1120 formWidget = XtParent(boardWidget);
1121 XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1122 XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1123 XtGetValues(optList[W_WHITE].handle, args, 2);
1124 if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1125 XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1126 XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1127 XtGetValues(optList[W_PAUSE].handle, args, 2);
1131 // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1132 // not need to go into InitDrawingSizes().
1136 // add accelerators to main shell
1137 gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1140 * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1142 WhiteIcon = LoadIconFile("icon_white");
1143 BlackIcon = LoadIconFile("icon_black");
1144 SetClockIcon(0); // sets white icon
1148 * Create a cursor for the board widget.
1151 window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1152 XChangeWindowAttributes(xDisplay, xBoardWindow,
1153 CWCursor, &window_attributes);
1157 * Inhibit shell resizing.
1160 shellArgs[0].value = (XtArgVal) &w;
1161 shellArgs[1].value = (XtArgVal) &h;
1162 XtGetValues(shellWidget, shellArgs, 2);
1163 shellArgs[4].value = shellArgs[2].value = w;
1164 shellArgs[5].value = shellArgs[3].value = h;
1165 // XtSetValues(shellWidget, &shellArgs[2], 4);
1168 // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1169 // It wil only become known asynchronously, when we first write a string into it.
1170 // This will then change the clock widget height, which triggers resizing the top-level window
1171 // and a configure event. Only then can we know the total height of the top-level window,
1172 // and calculate the height we need. The clockKludge flag suppresses all resizing until
1173 // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1176 gtk_widget_get_allocation(shells[BoardWindow], &a);
1177 w = a.width; h = a.height;
1178 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1179 clockKludge = hc = a.height;
1180 gtk_widget_get_allocation(boardWidget, &a);
1181 marginW = w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1182 marginH = h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1188 if(appData.logoSize)
1189 { // locate and read user logo
1191 snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1192 ASSIGN(userLogo, buf);
1195 if (appData.animate || appData.animateDragging)
1198 g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1199 g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1201 /* [AS] Restore layout */
1202 if( wpMoveHistory.visible ) {
1206 if( wpEvalGraph.visible )
1211 if( wpEngineOutput.visible ) {
1212 EngineOutputPopUp();
1215 if( wpConsole.visible && appData.icsActive ) {
1220 gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1225 if (errorExitStatus == -1) {
1226 if (appData.icsActive) {
1227 /* We now wait until we see "login:" from the ICS before
1228 sending the logon script (problems with timestamp otherwise) */
1229 /*ICSInitScript();*/
1230 if (appData.icsInputBox) ICSInputBoxPopUp();
1234 signal(SIGWINCH, TermSizeSigHandler);
1236 signal(SIGINT, IntSigHandler);
1237 signal(SIGTERM, IntSigHandler);
1238 if (*appData.cmailGameName != NULLCHAR) {
1239 signal(SIGUSR1, CmailSigHandler);
1244 // XtSetKeyboardFocus(shellWidget, formWidget);
1246 XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1249 /* check for GTK events and process them */
1252 gtk_main_iteration();
1255 if (appData.debugMode) fclose(debugFP); // [DM] debug
1262 while(gtk_events_pending()) gtk_main_iteration();
1266 TermSizeSigHandler (int sig)
1272 IntSigHandler (int sig)
1278 CmailSigHandler (int sig)
1283 signal(SIGUSR1, SIG_IGN); /* suspend handler */
1285 /* Activate call-back function CmailSigHandlerCallBack() */
1286 OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1288 signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1292 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1295 ReloadCmailMsgEvent(TRUE); /* Reload cmail msg */
1297 /**** end signal code ****/
1300 #define Abs(n) ((n)<0 ? -(n) : (n))
1303 InsertPxlSize (char *pattern, int targetPxlSize)
1306 snprintf(buf, MSG_SIZ, pattern, targetPxlSize); // pattern is something like "Sans Bold %d"
1313 InsertPxlSize (char *pattern, int targetPxlSize)
1315 char *base_fnt_lst, strInt[12], *p, *q;
1316 int alternatives, i, len, strIntLen;
1319 * Replace the "*" (if present) in the pixel-size slot of each
1320 * alternative with the targetPxlSize.
1324 while ((p = strchr(p, ',')) != NULL) {
1328 snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1329 strIntLen = strlen(strInt);
1330 base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1334 while (alternatives--) {
1335 char *comma = strchr(p, ',');
1336 for (i=0; i<14; i++) {
1337 char *hyphen = strchr(p, '-');
1339 if (comma && hyphen > comma) break;
1340 len = hyphen + 1 - p;
1341 if (i == 7 && *p == '*' && len == 2) {
1343 memcpy(q, strInt, strIntLen);
1353 len = comma + 1 - p;
1360 return base_fnt_lst;
1366 CreateFontSet (char *base_fnt_lst)
1369 char **missing_list;
1373 fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1374 &missing_list, &missing_count, &def_string);
1375 if (appData.debugMode) {
1377 XFontStruct **font_struct_list;
1378 char **font_name_list;
1379 fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1381 fprintf(debugFP, " got list %s, locale %s\n",
1382 XBaseFontNameListOfFontSet(fntSet),
1383 XLocaleOfFontSet(fntSet));
1384 count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1385 for (i = 0; i < count; i++) {
1386 fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1389 for (i = 0; i < missing_count; i++) {
1390 fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1393 if (fntSet == NULL) {
1394 fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1400 #else // not ENABLE_NLS
1402 * Find a font that matches "pattern" that is as close as
1403 * possible to the targetPxlSize. Prefer fonts that are k
1404 * pixels smaller to fonts that are k pixels larger. The
1405 * pattern must be in the X Consortium standard format,
1406 * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1407 * The return value should be freed with XtFree when no
1412 FindFont (char *pattern, int targetPxlSize)
1414 char **fonts, *p, *best, *scalable, *scalableTail;
1415 int i, j, nfonts, minerr, err, pxlSize;
1417 fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1419 fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1420 programName, pattern);
1427 for (i=0; i<nfonts; i++) {
1430 if (*p != '-') continue;
1432 if (*p == NULLCHAR) break;
1433 if (*p++ == '-') j++;
1435 if (j < 7) continue;
1438 scalable = fonts[i];
1441 err = pxlSize - targetPxlSize;
1442 if (Abs(err) < Abs(minerr) ||
1443 (minerr > 0 && err < 0 && -err == minerr)) {
1449 if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1450 /* If the error is too big and there is a scalable font,
1451 use the scalable font. */
1452 int headlen = scalableTail - scalable;
1453 p = (char *) XtMalloc(strlen(scalable) + 10);
1454 while (isdigit(*scalableTail)) scalableTail++;
1455 sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1457 p = (char *) XtMalloc(strlen(best) + 2);
1458 safeStrCpy(p, best, strlen(best)+1 );
1460 if (appData.debugMode) {
1461 fprintf(debugFP, "resolved %s at pixel size %d\n to %s\n",
1462 pattern, targetPxlSize, p);
1464 XFreeFontNames(fonts);
1471 MarkMenuItem (char *menuRef, int state)
1473 MenuItem *item = MenuNameToItem(menuRef);
1475 if(item && item->handle) {
1476 ((GtkCheckMenuItem *) (item->handle))->active = state;
1482 EnableNamedMenuItem (char *menuRef, int state)
1484 MenuItem *item = MenuNameToItem(menuRef);
1486 if(item && item->handle) gtk_widget_set_sensitive(item->handle, state);
1491 EnableButtonBar (int state)
1494 XtSetSensitive(optList[W_BUTTON].handle, state);
1500 SetMenuEnables (Enables *enab)
1502 while (enab->name != NULL) {
1503 EnableNamedMenuItem(enab->name, enab->value);
1508 gboolean KeyPressProc(window, eventkey, data)
1510 GdkEventKey *eventkey;
1514 MoveTypeInProc(eventkey); // pop up for typed in moves
1517 /* check for other key values */
1518 switch(eventkey->keyval) {
1530 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1531 { // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1533 if(*nprms == 0) return;
1534 item = MenuNameToItem(prms[0]);
1535 if(item) ((MenuProc *) item->proc) ();
1549 for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1550 entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1551 p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1552 dmEnables[i].piece);
1553 XtSetSensitive(entry, p != NULL || !appData.testLegality
1554 /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1555 && !appData.icsActive));
1557 while (p && *p++ == dmEnables[i].piece) count++;
1558 snprintf(label, sizeof(label), "%s %d", dmEnables[i].widget, count);
1560 XtSetArg(args[j], XtNlabel, label); j++;
1561 XtSetValues(entry, args, j);
1567 do_flash_delay (unsigned long msec)
1573 FlashDelay (int flash_delay)
1575 if(flash_delay) do_flash_delay(flash_delay);
1579 Fraction (int x, int start, int stop)
1581 double f = ((double) x - start)/(stop - start);
1582 if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1586 static WindowPlacement wpNew;
1589 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1591 int touch=0, fudge = 2, f = 2;
1592 GetActualPlacement(sh, wp);
1593 if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x) < fudge) touch = 1; else // right touch
1594 if(abs(wp->x + wp->width + 2*frameX + f - wpMain.x) < fudge) touch = 2; else // left touch
1595 if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1596 if(abs(wp->y + wp->height + frameX + frameY + f - wpMain.y) < fudge) touch = 4; // top touch
1597 //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);
1598 if(!touch ) return; // only windows that touch co-move
1599 if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1600 int heightInc = wpNew.height - wpMain.height;
1601 double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1602 double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1603 wp->y += fracTop * heightInc;
1604 heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1606 if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1608 wp->height += heightInc;
1609 } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1610 int widthInc = wpNew.width - wpMain.width;
1611 double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1612 double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1613 wp->y += fracLeft * widthInc;
1614 widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1616 if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1618 wp->width += widthInc;
1620 wp->x += wpNew.x - wpMain.x;
1621 wp->y += wpNew.y - wpMain.y;
1622 if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1623 if(touch == 3) wp->y += wpNew.height - wpMain.height;
1625 XtSetArg(args[j], XtNx, wp->x); j++;
1626 XtSetArg(args[j], XtNy, wp->y); j++;
1627 XtSetValues(sh, args, j);
1629 gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1630 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1631 gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1635 ReSize (WindowPlacement *wp)
1638 int sqx, sqy, w, h, hc, lg = lineGap;
1639 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1640 hc = a.height; // clock height can depend on single / double line clock text!
1641 if(clockKludge && hc != clockKludge) wp->height += hc - clockKludge, clockKludge = 0;
1642 wpMain.height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1643 if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1644 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1645 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1646 if(sqy < sqx) sqx = sqy;
1647 if(sqx < 20) return;
1648 if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1649 lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1650 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1651 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1652 if(sqy < sqx) sqx = sqy;
1654 if(sqx != squareSize) {
1655 squareSize = sqx; // adopt new square size
1656 CreatePNGPieces(); // make newly scaled pieces
1657 InitDrawingSizes(0, 0); // creates grid etc.
1658 } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1659 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1660 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1661 if(optList[W_BOARD].max > w) optList[W_BOARD].max = w;
1662 if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1665 static guint delayedDragTag = 0;
1674 // GetActualPlacement(shellWidget, &wpNew);
1675 if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1676 wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1677 busy = 0; return; // false alarm
1680 if(appData.useStickyWindows) {
1681 if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1682 if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1683 if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1684 if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1685 if(shellUp[ChatDlg]) CoDrag(shells[ChatDlg], &wpConsole);
1688 DrawPosition(True, NULL);
1689 if(delayedDragTag) g_source_remove(delayedDragTag);
1690 delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1697 //printf("old timr = %d\n", delayedDragTag);
1698 if(delayedDragTag) g_source_remove(delayedDragTag);
1699 delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1700 //printf("new timr = %d\n", delayedDragTag);
1704 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1706 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1708 wpNew.x = event->configure.x;
1709 wpNew.y = event->configure.y;
1710 wpNew.width = event->configure.width;
1711 wpNew.height = event->configure.height;
1712 DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1718 /* Disable all user input other than deleting the window */
1719 static int frozen = 0;
1725 /* Grab by a widget that doesn't accept input */
1726 gtk_grab_add(optList[W_MESSG].handle);
1730 /* Undo a FreezeUI */
1734 if (!frozen) return;
1735 gtk_grab_remove(optList[W_MESSG].handle);
1742 static int oldPausing = FALSE;
1743 static GameMode oldmode = (GameMode) -1;
1745 if (!boardWidget) return;
1747 if (pausing != oldPausing) {
1748 oldPausing = pausing;
1749 MarkMenuItem("Mode.Pause", pausing);
1751 if (appData.showButtonBar) {
1752 /* Always toggle, don't set. Previous code messes up when
1753 invoked while the button is pressed, as releasing it
1754 toggles the state again. */
1756 gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1757 gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1761 wname = ModeToWidgetName(oldmode);
1762 if (wname != NULL) {
1763 MarkMenuItem(wname, False);
1765 wname = ModeToWidgetName(gameMode);
1766 if (wname != NULL) {
1767 MarkMenuItem(wname, True);
1770 MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1772 /* Maybe all the enables should be handled here, not just this one */
1773 EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1775 DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1780 * Button/menu procedures
1783 void CopyFileToClipboard(gchar *filename)
1785 gchar *selection_tmp;
1789 FILE* f = fopen(filename, "r");
1792 if (f == NULL) return;
1796 selection_tmp = g_try_malloc(len + 1);
1797 if (selection_tmp == NULL) {
1798 printf("Malloc failed in CopyFileToClipboard\n");
1801 count = fread(selection_tmp, 1, len, f);
1804 g_free(selection_tmp);
1807 selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1809 // copy selection_tmp to clipboard
1810 GdkDisplay *gdisp = gdk_display_get_default();
1812 g_free(selection_tmp);
1815 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1816 gtk_clipboard_set_text(cb, selection_tmp, -1);
1817 g_free(selection_tmp);
1821 CopySomething (char *src)
1823 GdkDisplay *gdisp = gdk_display_get_default();
1825 if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1826 if (gdisp == NULL) return;
1827 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1828 gtk_clipboard_set_text(cb, src, -1);
1832 PastePositionProc ()
1834 GdkDisplay *gdisp = gdk_display_get_default();
1838 if (gdisp == NULL) return;
1839 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1840 fenstr = gtk_clipboard_wait_for_text(cb);
1841 if (fenstr==NULL) return; // nothing had been selected to copy
1842 EditPositionPasteFEN(fenstr);
1854 // get game from clipboard
1855 GdkDisplay *gdisp = gdk_display_get_default();
1856 if (gdisp == NULL) return;
1857 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1858 text = gtk_clipboard_wait_for_text(cb);
1859 if (text == NULL) return; // nothing to paste
1862 // write to temp file
1863 if (text == NULL || len == 0) {
1864 return; //nothing to paste
1866 f = fopen(gamePasteFilename, "w");
1868 DisplayError(_("Can't open temp file"), errno);
1871 fwrite(text, 1, len, f);
1875 LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1882 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1888 void MoveTypeInProc(eventkey)
1889 GdkEventKey *eventkey;
1893 // ingnore if ctrl, alt, or meta is pressed
1894 if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1898 buf[0]=eventkey->keyval;
1900 if (eventkey->keyval > 32 && eventkey->keyval < 256)
1901 ConsoleAutoPopUp (buf);
1906 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1908 if (!TempBackwardActive) {
1909 TempBackwardActive = True;
1915 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1917 /* Check to see if triggered by a key release event for a repeating key.
1918 * If so the next queued event will be a key press of the same key at the same time */
1919 if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1921 XPeekEvent(xDisplay, &next);
1922 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1923 next.xkey.keycode == event->xkey.keycode)
1927 TempBackwardActive = False;
1933 { // called from menu
1936 snprintf(buf, MSG_SIZ, "%s ./man.command", appData.sysOpen);
1939 system("xterm -e man xboard &");
1944 SetWindowTitle (char *text, char *title, char *icon)
1949 if (appData.titleInWindow) {
1951 XtSetArg(args[i], XtNlabel, text); i++;
1952 XtSetValues(titleWidget, args, i);
1955 XtSetArg(args[i], XtNiconName, (XtArgVal) icon); i++;
1956 XtSetArg(args[i], XtNtitle, (XtArgVal) title); i++;
1957 XtSetValues(shellWidget, args, i);
1958 XSync(xDisplay, False);
1960 if (appData.titleInWindow) {
1961 SetWidgetLabel(titleWidget, text);
1963 gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1968 DisplayIcsInteractionTitle (String message)
1971 if (oldICSInteractionTitle == NULL) {
1972 /* Magic to find the old window title, adapted from vim */
1973 char *wina = getenv("WINDOWID");
1975 Window win = (Window) atoi(wina);
1976 Window root, parent, *children;
1977 unsigned int nchildren;
1978 int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
1980 if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
1981 if (!XQueryTree(xDisplay, win, &root, &parent,
1982 &children, &nchildren)) break;
1983 if (children) XFree((void *)children);
1984 if (parent == root || parent == 0) break;
1987 XSetErrorHandler(oldHandler);
1989 if (oldICSInteractionTitle == NULL) {
1990 oldICSInteractionTitle = "xterm";
1993 printf("\033]0;%s\007", message);
2000 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
2002 GtkWidget *w = (GtkWidget *) opt->handle;
2009 strcpy(bgcolor, "black");
2010 strcpy(fgcolor, "white");
2012 strcpy(bgcolor, "white");
2013 strcpy(fgcolor, "black");
2016 appData.lowTimeWarning &&
2017 (timer / 1000) < appData.icsAlarmTime) {
2018 strcpy(fgcolor, appData.lowTimeWarningColor);
2021 gdk_color_parse( bgcolor, &col );
2022 gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
2024 if (appData.clockMode) {
2025 markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>", appData.clockFont,
2026 bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2027 // markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
2028 // bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2030 markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s </span>", appData.clockFont,
2031 bgcolor, fgcolor, color);
2032 // markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s </span>",
2033 // bgcolor, fgcolor, color);
2035 gtk_label_set_markup(GTK_LABEL(w), markup);
2039 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
2042 SetClockIcon (int color)
2044 GdkPixbuf *pm = *clockIcons[color];
2045 if (mainwindowIcon != pm) {
2046 mainwindowIcon = pm;
2048 gtkosx_application_set_dock_icon_pixbuf(theApp, mainwindowIcon);
2050 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
2055 #define INPUT_SOURCE_BUF_SIZE 8192
2064 char buf[INPUT_SOURCE_BUF_SIZE];
2069 DoInputCallback(io, cond, data)
2074 /* read input from one of the input source (for example a chess program, ICS, etc).
2075 * and call a function that will handle the input
2082 /* All information (callback function, file descriptor, etc) is
2083 * saved in an InputSource structure
2085 InputSource *is = (InputSource *) data;
2087 if (is->lineByLine) {
2088 count = read(is->fd, is->unused,
2089 INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
2091 if(count == 0 && is->kind == CPReal && shells[ChatDlg]) { // [HGM] absence of terminal is no error if ICS Console present
2092 RemoveInputSource(is); // cease reading stdin
2093 stdoutClosed = TRUE; // suppress future output
2096 (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
2099 is->unused += count;
2101 /* break input into lines and call the callback function on each
2104 while (p < is->unused) {
2105 q = memchr(p, '\n', is->unused - p);
2106 if (q == NULL) break;
2108 (is->func)(is, is->closure, p, q - p, 0);
2111 /* remember not yet used part of the buffer */
2113 while (p < is->unused) {
2118 /* read maximum length of input buffer and send the whole buffer
2119 * to the callback function
2121 count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2126 (is->func)(is, is->closure, is->buf, count, error);
2128 return True; // Must return true or the watch will be removed
2131 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2138 GIOChannel *channel;
2139 ChildProc *cp = (ChildProc *) pr;
2141 is = (InputSource *) calloc(1, sizeof(InputSource));
2142 is->lineByLine = lineByLine;
2146 is->fd = fileno(stdin);
2148 is->kind = cp->kind;
2149 is->fd = cp->fdFrom;
2152 is->unused = is->buf;
2156 /* GTK-TODO: will this work on windows?*/
2158 channel = g_io_channel_unix_new(is->fd);
2159 g_io_channel_set_close_on_unref (channel, TRUE);
2160 is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2162 is->closure = closure;
2163 return (InputSourceRef) is;
2168 RemoveInputSource(isr)
2171 InputSource *is = (InputSource *) isr;
2173 if (is->sid == 0) return;
2174 g_source_remove(is->sid);
2181 static Boolean frameWaiting;
2184 FrameAlarm (int sig)
2186 frameWaiting = False;
2187 /* In case System-V style signals. Needed?? */
2188 signal(SIGALRM, FrameAlarm);
2192 FrameDelay (int time)
2194 struct itimerval delay;
2197 frameWaiting = True;
2198 signal(SIGALRM, FrameAlarm);
2199 delay.it_interval.tv_sec =
2200 delay.it_value.tv_sec = time / 1000;
2201 delay.it_interval.tv_usec =
2202 delay.it_value.tv_usec = (time % 1000) * 1000;
2203 setitimer(ITIMER_REAL, &delay, NULL);
2204 while (frameWaiting) pause();
2205 delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2206 delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2207 setitimer(ITIMER_REAL, &delay, NULL);
2214 FrameDelay (int time)
2217 XSync(xDisplay, False);
2219 // gtk_main_iteration_do(False);
2222 usleep(time * 1000);
2228 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2230 char buf[MSG_SIZ], *logoName = buf;
2232 if(appData.logo[n][0]) {
2233 logoName = appData.logo[n];
2234 } else if(appData.autoLogo) {
2235 if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2236 sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2237 } else { // engine; look in engine-dir (if any) first
2238 snprintf(buf, MSG_SIZ, "%s/logo.png", appData.directory[n]);
2239 if(appData.directory[n] && appData.directory[n][0]
2240 && strcmp(appData.directory[n], ".") && (f = fopen(buf, "r")) )
2242 else // no engine dir or no logo.png in it: look in logo dir
2243 if(appData.logoDir && appData.logoDir[0])
2244 sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2248 { ASSIGN(cps->programLogo, logoName); }
2252 UpdateLogos (int displ)
2254 if(optList[W_WHITE-1].handle == NULL) return;
2255 LoadLogo(&first, 0, 0);
2256 LoadLogo(&second, 1, appData.icsActive);
2257 if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2261 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2272 GtkFileFilter *gtkfilter;
2273 GtkFileFilter *gtkfilter_all;
2275 char fileext[10] = "";
2276 char *result = NULL;
2279 /* make a copy of the filter string, so that strtok can work with it*/
2280 cp = strdup(filter);
2282 /* add filters for file extensions */
2283 gtkfilter = gtk_file_filter_new();
2284 gtkfilter_all = gtk_file_filter_new();
2286 /* one filter to show everything */
2287 gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2288 gtk_file_filter_set_name (gtkfilter_all, "All Files");
2290 /* add filter if present */
2291 result = strtok(cp, space);
2292 while( result != NULL ) {
2293 snprintf(fileext,10,"*%s",result);
2294 result = strtok( NULL, space );
2295 gtk_file_filter_add_pattern(gtkfilter, fileext);
2298 /* second filter to only show what's useful */
2299 gtk_file_filter_set_name (gtkfilter,filter);
2301 if (openMode[0] == 'r')
2303 dialog = gtk_file_chooser_dialog_new (label,
2305 GTK_FILE_CHOOSER_ACTION_OPEN,
2306 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2307 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2312 dialog = gtk_file_chooser_dialog_new (label,
2314 GTK_FILE_CHOOSER_ACTION_SAVE,
2315 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2316 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2318 /* add filename suggestions */
2319 if (strlen(def) > 0 )
2320 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2322 //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2326 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2327 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2328 /* activate filter */
2329 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2331 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2336 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2339 f = fopen(filename, openMode);
2342 DisplayError(_("Failed to open file"), errno);
2346 /* TODO add indec */
2348 ASSIGN(*name, filename);
2349 ScheduleDelayedEvent(DelayedLoad, 50);
2354 gtk_widget_destroy (dialog);