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 GdkScreen *screen = gdk_screen_get_default();
1033 guint screenwidth = gdk_screen_get_width(screen);
1034 guint screenheight = gdk_screen_get_height(screen);
1035 while (screenwidth < (szd->minScreenSize*BOARD_WIDTH + 4)/8 ||
1036 screenheight < (szd->minScreenSize*BOARD_HEIGHT + 4)/8) {
1039 if (szd->name == NULL) szd--;
1040 appData.boardSize = strdup(szd->name); // [HGM] settings: remember name for saving settings
1042 while (szd->name != NULL &&
1043 StrCaseCmp(szd->name, appData.boardSize) != 0) szd++;
1044 if (szd->name == NULL) {
1045 fprintf(stderr, _("%s: unrecognized boardSize name %s\n"),
1046 programName, appData.boardSize);
1050 squareSize = szd->squareSize;
1051 lineGap = szd->lineGap;
1052 clockFontPxlSize = szd->clockFontPxlSize;
1053 coordFontPxlSize = szd->coordFontPxlSize;
1054 fontPxlSize = szd->fontPxlSize;
1055 smallLayout = szd->smallLayout;
1056 tinyLayout = szd->tinyLayout;
1057 // [HGM] font: use defaults from settings file if available and not overruled
1060 defaultLineGap = lineGap;
1061 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
1063 /* [HR] height treated separately (hacked) */
1064 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
1065 // boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
1068 * Determine what fonts to use.
1070 InitializeFonts((2*clockFontPxlSize+1)/3, coordFontPxlSize, fontPxlSize);
1073 * Detect if there are not enough colors available and adapt.
1076 if (DefaultDepth(xDisplay, xScreen) <= 2) {
1077 appData.monoMode = True;
1081 forceMono = MakeColors();
1084 fprintf(stderr, _("%s: too few colors available; trying monochrome mode\n"),
1086 appData.monoMode = True;
1089 ParseIcsTextColors();
1095 layoutName = "tinyLayout";
1096 } else if (smallLayout) {
1097 layoutName = "smallLayout";
1099 layoutName = "normalLayout";
1102 wpMain.width = -1; // prevent popup sizes window
1103 optList = BoardPopUp(squareSize, lineGap, (void*)
1113 InitDrawingHandle(optList + W_BOARD);
1114 shellWidget = shells[BoardWindow];
1115 currBoard = &optList[W_BOARD];
1116 boardWidget = optList[W_BOARD].handle;
1117 menuBarWidget = optList[W_MENU].handle;
1118 dropMenu = optList[W_DROP].handle;
1119 titleWidget = optList[optList[W_TITLE].type != -1 ? W_TITLE : W_SMALL].handle;
1121 formWidget = XtParent(boardWidget);
1122 XtSetArg(args[0], XtNbackground, &timerBackgroundPixel);
1123 XtSetArg(args[1], XtNforeground, &timerForegroundPixel);
1124 XtGetValues(optList[W_WHITE].handle, args, 2);
1125 if (appData.showButtonBar) { // can't we use timer pixels for this? (Or better yet, just black & white?)
1126 XtSetArg(args[0], XtNbackground, &buttonBackgroundPixel);
1127 XtSetArg(args[1], XtNforeground, &buttonForegroundPixel);
1128 XtGetValues(optList[W_PAUSE].handle, args, 2);
1132 // [HGM] it seems the layout code ends here, but perhaps the color stuff is size independent and would
1133 // not need to go into InitDrawingSizes().
1137 // add accelerators to main shell
1138 gtk_window_add_accel_group(GTK_WINDOW(shellWidget), GtkAccelerators);
1141 * Create an icon. (Use two icons, to indicate whther it is white's or black's turn.)
1143 WhiteIcon = LoadIconFile("icon_white");
1144 BlackIcon = LoadIconFile("icon_black");
1145 SetClockIcon(0); // sets white icon
1149 * Create a cursor for the board widget.
1152 window_attributes.cursor = XCreateFontCursor(xDisplay, XC_hand2);
1153 XChangeWindowAttributes(xDisplay, xBoardWindow,
1154 CWCursor, &window_attributes);
1158 * Inhibit shell resizing.
1161 shellArgs[0].value = (XtArgVal) &w;
1162 shellArgs[1].value = (XtArgVal) &h;
1163 XtGetValues(shellWidget, shellArgs, 2);
1164 shellArgs[4].value = shellArgs[2].value = w;
1165 shellArgs[5].value = shellArgs[3].value = h;
1166 // XtSetValues(shellWidget, &shellArgs[2], 4);
1169 // Note: We cannot do sensible sizing here, because the height of the clock widget is not yet known
1170 // It wil only become known asynchronously, when we first write a string into it.
1171 // This will then change the clock widget height, which triggers resizing the top-level window
1172 // and a configure event. Only then can we know the total height of the top-level window,
1173 // and calculate the height we need. The clockKludge flag suppresses all resizing until
1174 // that moment comes, after which the configure event-handler handles it through a (delayed) DragProg.
1177 gtk_widget_get_allocation(shells[BoardWindow], &a);
1178 w = a.width; h = a.height;
1179 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1180 clockKludge = hc = a.height;
1181 gtk_widget_get_allocation(boardWidget, &a);
1182 marginW = w - boardWidth; // [HGM] needed to set new shellWidget size when we resize board
1183 marginH = h - a.height - hc; // subtract current clock height, so it can be added back dynamically
1189 if(appData.logoSize)
1190 { // locate and read user logo
1192 snprintf(buf, MSG_SIZ, "%s/%s.png", appData.logoDir, UserName());
1193 ASSIGN(userLogo, buf);
1196 if (appData.animate || appData.animateDragging)
1199 g_signal_connect(shells[BoardWindow], "key-press-event", G_CALLBACK(KeyPressProc), NULL);
1200 g_signal_connect(shells[BoardWindow], "configure-event", G_CALLBACK(EventProc), NULL);
1202 /* [AS] Restore layout */
1203 if( wpMoveHistory.visible ) {
1207 if( wpEvalGraph.visible )
1212 if( wpEngineOutput.visible ) {
1213 EngineOutputPopUp();
1216 if( wpConsole.visible && appData.icsActive ) {
1221 gameInfo.boardWidth = 0; // [HGM] pieces: kludge to ensure InitPosition() calls InitDrawingSizes()
1226 if (errorExitStatus == -1) {
1227 if (appData.icsActive) {
1228 /* We now wait until we see "login:" from the ICS before
1229 sending the logon script (problems with timestamp otherwise) */
1230 /*ICSInitScript();*/
1231 if (appData.icsInputBox) ICSInputBoxPopUp();
1235 signal(SIGWINCH, TermSizeSigHandler);
1237 signal(SIGINT, IntSigHandler);
1238 signal(SIGTERM, IntSigHandler);
1239 if (*appData.cmailGameName != NULLCHAR) {
1240 signal(SIGUSR1, CmailSigHandler);
1245 // XtSetKeyboardFocus(shellWidget, formWidget);
1247 XSetInputFocus(xDisplay, XtWindow(formWidget), RevertToPointerRoot, CurrentTime);
1250 /* check for GTK events and process them */
1253 gtk_main_iteration();
1256 if (appData.debugMode) fclose(debugFP); // [DM] debug
1263 while(gtk_events_pending()) gtk_main_iteration();
1267 TermSizeSigHandler (int sig)
1273 IntSigHandler (int sig)
1279 CmailSigHandler (int sig)
1284 signal(SIGUSR1, SIG_IGN); /* suspend handler */
1286 /* Activate call-back function CmailSigHandlerCallBack() */
1287 OutputToProcess(cmailPR, (char *)(&dummy), sizeof(int), &error);
1289 signal(SIGUSR1, CmailSigHandler); /* re-activate handler */
1293 CmailSigHandlerCallBack (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1296 ReloadCmailMsgEvent(TRUE); /* Reload cmail msg */
1298 /**** end signal code ****/
1301 #define Abs(n) ((n)<0 ? -(n) : (n))
1304 InsertPxlSize (char *pattern, int targetPxlSize)
1307 snprintf(buf, MSG_SIZ, pattern, targetPxlSize); // pattern is something like "Sans Bold %d"
1314 InsertPxlSize (char *pattern, int targetPxlSize)
1316 char *base_fnt_lst, strInt[12], *p, *q;
1317 int alternatives, i, len, strIntLen;
1320 * Replace the "*" (if present) in the pixel-size slot of each
1321 * alternative with the targetPxlSize.
1325 while ((p = strchr(p, ',')) != NULL) {
1329 snprintf(strInt, sizeof(strInt), "%d", targetPxlSize);
1330 strIntLen = strlen(strInt);
1331 base_fnt_lst = calloc(1, strlen(pattern) + strIntLen * alternatives + 1);
1335 while (alternatives--) {
1336 char *comma = strchr(p, ',');
1337 for (i=0; i<14; i++) {
1338 char *hyphen = strchr(p, '-');
1340 if (comma && hyphen > comma) break;
1341 len = hyphen + 1 - p;
1342 if (i == 7 && *p == '*' && len == 2) {
1344 memcpy(q, strInt, strIntLen);
1354 len = comma + 1 - p;
1361 return base_fnt_lst;
1367 CreateFontSet (char *base_fnt_lst)
1370 char **missing_list;
1374 fntSet = XCreateFontSet(xDisplay, base_fnt_lst,
1375 &missing_list, &missing_count, &def_string);
1376 if (appData.debugMode) {
1378 XFontStruct **font_struct_list;
1379 char **font_name_list;
1380 fprintf(debugFP, "Requested font set for list %s\n", base_fnt_lst);
1382 fprintf(debugFP, " got list %s, locale %s\n",
1383 XBaseFontNameListOfFontSet(fntSet),
1384 XLocaleOfFontSet(fntSet));
1385 count = XFontsOfFontSet(fntSet, &font_struct_list, &font_name_list);
1386 for (i = 0; i < count; i++) {
1387 fprintf(debugFP, " got charset %s\n", font_name_list[i]);
1390 for (i = 0; i < missing_count; i++) {
1391 fprintf(debugFP, " missing charset %s\n", missing_list[i]);
1394 if (fntSet == NULL) {
1395 fprintf(stderr, _("Unable to create font set for %s.\n"), base_fnt_lst);
1401 #else // not ENABLE_NLS
1403 * Find a font that matches "pattern" that is as close as
1404 * possible to the targetPxlSize. Prefer fonts that are k
1405 * pixels smaller to fonts that are k pixels larger. The
1406 * pattern must be in the X Consortium standard format,
1407 * e.g. "-*-helvetica-bold-r-normal--*-*-*-*-*-*-*-*".
1408 * The return value should be freed with XtFree when no
1413 FindFont (char *pattern, int targetPxlSize)
1415 char **fonts, *p, *best, *scalable, *scalableTail;
1416 int i, j, nfonts, minerr, err, pxlSize;
1418 fonts = XListFonts(xDisplay, pattern, 999999, &nfonts);
1420 fprintf(stderr, _("%s: no fonts match pattern %s\n"),
1421 programName, pattern);
1428 for (i=0; i<nfonts; i++) {
1431 if (*p != '-') continue;
1433 if (*p == NULLCHAR) break;
1434 if (*p++ == '-') j++;
1436 if (j < 7) continue;
1439 scalable = fonts[i];
1442 err = pxlSize - targetPxlSize;
1443 if (Abs(err) < Abs(minerr) ||
1444 (minerr > 0 && err < 0 && -err == minerr)) {
1450 if (scalable && Abs(minerr) > appData.fontSizeTolerance) {
1451 /* If the error is too big and there is a scalable font,
1452 use the scalable font. */
1453 int headlen = scalableTail - scalable;
1454 p = (char *) XtMalloc(strlen(scalable) + 10);
1455 while (isdigit(*scalableTail)) scalableTail++;
1456 sprintf(p, "%.*s%d%s", headlen, scalable, targetPxlSize, scalableTail);
1458 p = (char *) XtMalloc(strlen(best) + 2);
1459 safeStrCpy(p, best, strlen(best)+1 );
1461 if (appData.debugMode) {
1462 fprintf(debugFP, "resolved %s at pixel size %d\n to %s\n",
1463 pattern, targetPxlSize, p);
1465 XFreeFontNames(fonts);
1472 MarkMenuItem (char *menuRef, int state)
1474 MenuItem *item = MenuNameToItem(menuRef);
1476 if(item && item->handle) {
1477 ((GtkCheckMenuItem *) (item->handle))->active = state;
1483 EnableNamedMenuItem (char *menuRef, int state)
1485 MenuItem *item = MenuNameToItem(menuRef);
1487 if(item && item->handle) gtk_widget_set_sensitive(item->handle, state);
1492 EnableButtonBar (int state)
1495 XtSetSensitive(optList[W_BUTTON].handle, state);
1501 SetMenuEnables (Enables *enab)
1503 while (enab->name != NULL) {
1504 EnableNamedMenuItem(enab->name, enab->value);
1509 gboolean KeyPressProc(window, eventkey, data)
1511 GdkEventKey *eventkey;
1515 MoveTypeInProc(eventkey); // pop up for typed in moves
1518 /* check for other key values */
1519 switch(eventkey->keyval) {
1531 KeyBindingProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1532 { // [HGM] new method of key binding: specify MenuItem(FlipView) in stead of FlipViewProc in translation string
1534 if(*nprms == 0) return;
1535 item = MenuNameToItem(prms[0]);
1536 if(item) ((MenuProc *) item->proc) ();
1550 for (i=0; i<sizeof(dmEnables)/sizeof(DropMenuEnables); i++) {
1551 entry = XtNameToWidget(dropMenu, dmEnables[i].widget);
1552 p = strchr(gameMode == IcsPlayingWhite ? white_holding : black_holding,
1553 dmEnables[i].piece);
1554 XtSetSensitive(entry, p != NULL || !appData.testLegality
1555 /*!!temp:*/ || (gameInfo.variant == VariantCrazyhouse
1556 && !appData.icsActive));
1558 while (p && *p++ == dmEnables[i].piece) count++;
1559 snprintf(label, sizeof(label), "%s %d", dmEnables[i].widget, count);
1561 XtSetArg(args[j], XtNlabel, label); j++;
1562 XtSetValues(entry, args, j);
1568 do_flash_delay (unsigned long msec)
1574 FlashDelay (int flash_delay)
1576 if(flash_delay) do_flash_delay(flash_delay);
1580 Fraction (int x, int start, int stop)
1582 double f = ((double) x - start)/(stop - start);
1583 if(f > 1.) f = 1.; else if(f < 0.) f = 0.;
1587 static WindowPlacement wpNew;
1590 CoDrag (GtkWidget *sh, WindowPlacement *wp)
1592 int touch=0, fudge = 4, f = 3;
1593 GetActualPlacement(sh, wp);
1594 if(abs(wpMain.x + wpMain.width + 2*frameX - f - wp->x) < fudge) touch = 1; else // right touch
1595 if(abs(wp->x + wp->width + 2*frameX - f - wpMain.x) < fudge) touch = 2; else // left touch
1596 if(abs(wpMain.y + wpMain.height + frameX - f + frameY - wp->y) < fudge) touch = 3; else // bottom touch
1597 if(abs(wp->y + wp->height + frameX + frameY - f - wpMain.y) < fudge) touch = 4; // top touch
1598 //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);
1599 if(!touch ) return; // only windows that touch co-move
1600 if(touch < 3 && wpNew.height != wpMain.height) { // left or right and height changed
1601 int heightInc = wpNew.height - wpMain.height;
1602 double fracTop = Fraction(wp->y, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1603 double fracBot = Fraction(wp->y + wp->height + frameX + frameY + 1, wpMain.y, wpMain.y + wpMain.height + frameX + frameY);
1604 wp->y += fracTop * heightInc;
1605 heightInc = (int) (fracBot * heightInc) - (int) (fracTop * heightInc);
1607 if(heightInc) XtSetArg(args[j], XtNheight, wp->height + heightInc), j++;
1609 wp->height += heightInc;
1610 } else if(touch > 2 && wpNew.width != wpMain.width) { // top or bottom and width changed
1611 int widthInc = wpNew.width - wpMain.width;
1612 double fracLeft = Fraction(wp->x, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1613 double fracRght = Fraction(wp->x + wp->width + 2*frameX + 1, wpMain.x, wpMain.x + wpMain.width + 2*frameX);
1614 wp->y += fracLeft * widthInc;
1615 widthInc = (int) (fracRght * widthInc) - (int) (fracLeft * widthInc);
1617 if(widthInc) XtSetArg(args[j], XtNwidth, wp->width + widthInc), j++;
1619 wp->width += widthInc;
1621 wp->x += wpNew.x - wpMain.x;
1622 wp->y += wpNew.y - wpMain.y;
1623 if(touch == 1) wp->x += wpNew.width - wpMain.width; else
1624 if(touch == 3) wp->y += wpNew.height - wpMain.height;
1626 XtSetArg(args[j], XtNx, wp->x); j++;
1627 XtSetArg(args[j], XtNy, wp->y); j++;
1628 XtSetValues(sh, args, j);
1630 gtk_window_move(GTK_WINDOW(sh), wp->x, wp->y);
1631 //printf("moved to (%d,%d)\n", wp->x, wp->y);
1632 gtk_window_resize(GTK_WINDOW(sh), wp->width, wp->height);
1636 ReSize (WindowPlacement *wp)
1639 int sqx, sqy, w, h, hc, lg = lineGap;
1640 gtk_widget_get_allocation(optList[W_WHITE].handle, &a);
1641 hc = a.height; // clock height can depend on single / double line clock text!
1642 if(clockKludge && hc != clockKludge) wp->height += hc - clockKludge, clockKludge = 0;
1643 wpMain.height = BOARD_HEIGHT * (squareSize + lineGap) + lineGap + marginH + hc;
1644 if(wp->width == wpMain.width && wp->height == wpMain.height) return; // not sized
1645 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1646 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1647 if(sqy < sqx) sqx = sqy;
1648 if(sqx < 20) return;
1649 if(appData.overrideLineGap < 0) { // do second iteration with adjusted lineGap
1651 lg = lineGap = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1652 sqx = (wp->width - lg - marginW) / BOARD_WIDTH - lg;
1653 sqy = (wp->height - lg - marginH - hc) / BOARD_HEIGHT - lg;
1654 if(sqy < sqx) sqx = sqy;
1655 lg = sqx < 37 ? 1 : sqx < 59 ? 2 : sqx < 116 ? 3 : 4;
1656 if(sqx == oldSqx + 1 && lg == lineGap + 1) sqx = oldSqx, squareSize = 0; // prevent oscillations, force resize by kludge
1658 if(sqx != squareSize) {
1659 squareSize = sqx; // adopt new square size
1660 CreatePNGPieces(); // make newly scaled pieces
1661 InitDrawingSizes(0, 0); // creates grid etc.
1662 } else ResizeBoardWindow(BOARD_WIDTH * (squareSize + lineGap) + lineGap, BOARD_HEIGHT * (squareSize + lineGap) + lineGap, 0);
1663 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
1664 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
1665 if(optList[W_BOARD].max > w) optList[W_BOARD].max = w;
1666 if(optList[W_BOARD].value > h) optList[W_BOARD].value = h;
1669 static guint delayedDragTag = 0;
1678 // GetActualPlacement(shellWidget, &wpNew);
1679 if(wpNew.x == wpMain.x && wpNew.y == wpMain.y && // not moved
1680 wpNew.width == wpMain.width && wpNew.height == wpMain.height) { // not sized
1681 busy = 0; return; // false alarm
1684 if(appData.useStickyWindows) {
1685 if(shellUp[EngOutDlg]) CoDrag(shells[EngOutDlg], &wpEngineOutput);
1686 if(shellUp[HistoryDlg]) CoDrag(shells[HistoryDlg], &wpMoveHistory);
1687 if(shellUp[EvalGraphDlg]) CoDrag(shells[EvalGraphDlg], &wpEvalGraph);
1688 if(shellUp[GameListDlg]) CoDrag(shells[GameListDlg], &wpGameList);
1689 if(shellUp[ChatDlg]) CoDrag(shells[ChatDlg], &wpConsole);
1692 DrawPosition(True, NULL);
1693 if(delayedDragTag) g_source_remove(delayedDragTag);
1694 delayedDragTag = 0; // now drag executed, make sure next DelayedDrag will not cancel timer event (which could now be used by other)
1701 //printf("old timr = %d\n", delayedDragTag);
1702 if(delayedDragTag) g_source_remove(delayedDragTag);
1703 delayedDragTag = g_timeout_add( 200, (GSourceFunc) DragProc, NULL);
1704 //printf("new timr = %d\n", delayedDragTag);
1708 EventProc (GtkWidget *widget, GdkEvent *event, gpointer g)
1710 //printf("event proc (%d,%d) %dx%d\n", event->configure.x, event->configure.y, event->configure.width, event->configure.height);
1712 wpNew.x = event->configure.x;
1713 wpNew.y = event->configure.y;
1714 wpNew.width = event->configure.width;
1715 wpNew.height = event->configure.height;
1716 DelayedDrag(); // as long as events keep coming in faster than 50 msec, they destroy each other
1722 /* Disable all user input other than deleting the window */
1723 static int frozen = 0;
1729 /* Grab by a widget that doesn't accept input */
1730 gtk_grab_add(optList[W_MESSG].handle);
1734 /* Undo a FreezeUI */
1738 if (!frozen) return;
1739 gtk_grab_remove(optList[W_MESSG].handle);
1746 static int oldPausing = FALSE;
1747 static GameMode oldmode = (GameMode) -1;
1749 if (!boardWidget) return;
1751 if (pausing != oldPausing) {
1752 oldPausing = pausing;
1753 MarkMenuItem("Mode.Pause", pausing);
1755 if (appData.showButtonBar) {
1756 /* Always toggle, don't set. Previous code messes up when
1757 invoked while the button is pressed, as releasing it
1758 toggles the state again. */
1760 gdk_color_parse( pausing ? "#808080" : "#F0F0F0", &color );
1761 gtk_widget_modify_bg ( GTK_WIDGET(optList[W_PAUSE].handle), GTK_STATE_NORMAL, &color );
1765 wname = ModeToWidgetName(oldmode);
1766 if (wname != NULL) {
1767 MarkMenuItem(wname, False);
1769 wname = ModeToWidgetName(gameMode);
1770 if (wname != NULL) {
1771 MarkMenuItem(wname, True);
1774 MarkMenuItem("Mode.MachineMatch", matchMode && matchGame < appData.matchGames);
1776 /* Maybe all the enables should be handled here, not just this one */
1777 EnableNamedMenuItem("Mode.Training", gameMode == Training || gameMode == PlayFromGameFile);
1779 DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
1784 * Button/menu procedures
1787 void CopyFileToClipboard(gchar *filename)
1789 gchar *selection_tmp;
1793 FILE* f = fopen(filename, "r");
1796 if (f == NULL) return;
1800 selection_tmp = g_try_malloc(len + 1);
1801 if (selection_tmp == NULL) {
1802 printf("Malloc failed in CopyFileToClipboard\n");
1805 count = fread(selection_tmp, 1, len, f);
1808 g_free(selection_tmp);
1811 selection_tmp[len] = NULLCHAR; // file is now in selection_tmp
1813 // copy selection_tmp to clipboard
1814 GdkDisplay *gdisp = gdk_display_get_default();
1816 g_free(selection_tmp);
1819 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1820 gtk_clipboard_set_text(cb, selection_tmp, -1);
1821 g_free(selection_tmp);
1825 CopySomething (char *src)
1827 GdkDisplay *gdisp = gdk_display_get_default();
1829 if(!src) { CopyFileToClipboard(gameCopyFilename); return; }
1830 if (gdisp == NULL) return;
1831 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1832 gtk_clipboard_set_text(cb, src, -1);
1836 PastePositionProc ()
1838 GdkDisplay *gdisp = gdk_display_get_default();
1842 if (gdisp == NULL) return;
1843 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1844 fenstr = gtk_clipboard_wait_for_text(cb);
1845 if (fenstr==NULL) return; // nothing had been selected to copy
1846 EditPositionPasteFEN(fenstr);
1858 // get game from clipboard
1859 GdkDisplay *gdisp = gdk_display_get_default();
1860 if (gdisp == NULL) return;
1861 cb = gtk_clipboard_get_for_display(gdisp, GDK_SELECTION_CLIPBOARD);
1862 text = gtk_clipboard_wait_for_text(cb);
1863 if (text == NULL) return; // nothing to paste
1866 // write to temp file
1867 if (text == NULL || len == 0) {
1868 return; //nothing to paste
1870 f = fopen(gamePasteFilename, "w");
1872 DisplayError(_("Can't open temp file"), errno);
1875 fwrite(text, 1, len, f);
1879 LoadGameFromFile(gamePasteFilename, 0, gamePasteFilename, TRUE);
1886 QuitWrapper (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1892 void MoveTypeInProc(eventkey)
1893 GdkEventKey *eventkey;
1897 // ingnore if ctrl, alt, or meta is pressed
1898 if (eventkey->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK)) {
1902 buf[0]=eventkey->keyval;
1904 if (eventkey->keyval > 32 && eventkey->keyval < 256)
1905 ConsoleAutoPopUp (buf);
1910 TempBackwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1912 if (!TempBackwardActive) {
1913 TempBackwardActive = True;
1919 TempForwardProc (Widget w, XEvent *event, String *prms, Cardinal *nprms)
1921 /* Check to see if triggered by a key release event for a repeating key.
1922 * If so the next queued event will be a key press of the same key at the same time */
1923 if (XEventsQueued(xDisplay, QueuedAfterReading)) {
1925 XPeekEvent(xDisplay, &next);
1926 if (next.type == KeyPress && next.xkey.time == event->xkey.time &&
1927 next.xkey.keycode == event->xkey.keycode)
1931 TempBackwardActive = False;
1937 { // called from menu
1940 snprintf(buf, MSG_SIZ, "%s ./man.command", appData.sysOpen);
1943 system("xterm -e man xboard &");
1948 SetWindowTitle (char *text, char *title, char *icon)
1953 if (appData.titleInWindow) {
1955 XtSetArg(args[i], XtNlabel, text); i++;
1956 XtSetValues(titleWidget, args, i);
1959 XtSetArg(args[i], XtNiconName, (XtArgVal) icon); i++;
1960 XtSetArg(args[i], XtNtitle, (XtArgVal) title); i++;
1961 XtSetValues(shellWidget, args, i);
1962 XSync(xDisplay, False);
1964 if (appData.titleInWindow) {
1965 SetWidgetLabel(titleWidget, text);
1967 gtk_window_set_title (GTK_WINDOW(shells[BoardWindow]), title);
1972 DisplayIcsInteractionTitle (String message)
1975 if (oldICSInteractionTitle == NULL) {
1976 /* Magic to find the old window title, adapted from vim */
1977 char *wina = getenv("WINDOWID");
1979 Window win = (Window) atoi(wina);
1980 Window root, parent, *children;
1981 unsigned int nchildren;
1982 int (*oldHandler)() = XSetErrorHandler(NullXErrorCheck);
1984 if (XFetchName(xDisplay, win, &oldICSInteractionTitle)) break;
1985 if (!XQueryTree(xDisplay, win, &root, &parent,
1986 &children, &nchildren)) break;
1987 if (children) XFree((void *)children);
1988 if (parent == root || parent == 0) break;
1991 XSetErrorHandler(oldHandler);
1993 if (oldICSInteractionTitle == NULL) {
1994 oldICSInteractionTitle = "xterm";
1997 printf("\033]0;%s\007", message);
2004 DisplayTimerLabel (Option *opt, char *color, long timer, int highlight)
2006 GtkWidget *w = (GtkWidget *) opt->handle;
2013 strcpy(bgcolor, "black");
2014 strcpy(fgcolor, "white");
2016 strcpy(bgcolor, "white");
2017 strcpy(fgcolor, "black");
2020 appData.lowTimeWarning &&
2021 (timer / 1000) < appData.icsAlarmTime) {
2022 strcpy(fgcolor, appData.lowTimeWarningColor);
2025 gdk_color_parse( bgcolor, &col );
2026 gtk_widget_modify_bg(gtk_widget_get_parent(opt->handle), GTK_STATE_NORMAL, &col);
2028 if (appData.clockMode) {
2029 markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>", appData.clockFont,
2030 bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2031 // markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s:%s%s</span>",
2032 // bgcolor, fgcolor, color, appData.logoSize && !partnerUp ? "\n" : " ", TimeString(timer));
2034 markup = g_markup_printf_escaped("<span font=\"%s\" background=\"%s\" foreground=\"%s\">%s </span>", appData.clockFont,
2035 bgcolor, fgcolor, color);
2036 // markup = g_markup_printf_escaped("<span size=\"xx-large\" weight=\"heavy\" background=\"%s\" foreground=\"%s\">%s </span>",
2037 // bgcolor, fgcolor, color);
2039 gtk_label_set_markup(GTK_LABEL(w), markup);
2043 static GdkPixbuf **clockIcons[] = { &WhiteIcon, &BlackIcon };
2046 SetClockIcon (int color)
2048 GdkPixbuf *pm = *clockIcons[color];
2049 if (mainwindowIcon != pm) {
2050 mainwindowIcon = pm;
2052 gtkosx_application_set_dock_icon_pixbuf(theApp, mainwindowIcon);
2054 gtk_window_set_icon(GTK_WINDOW(shellWidget), mainwindowIcon);
2059 #define INPUT_SOURCE_BUF_SIZE 8192
2068 char buf[INPUT_SOURCE_BUF_SIZE];
2073 DoInputCallback(io, cond, data)
2078 /* read input from one of the input source (for example a chess program, ICS, etc).
2079 * and call a function that will handle the input
2086 /* All information (callback function, file descriptor, etc) is
2087 * saved in an InputSource structure
2089 InputSource *is = (InputSource *) data;
2091 if (is->lineByLine) {
2092 count = read(is->fd, is->unused,
2093 INPUT_SOURCE_BUF_SIZE - (is->unused - is->buf));
2095 if(count == 0 && is->kind == CPReal && shells[ChatDlg]) { // [HGM] absence of terminal is no error if ICS Console present
2096 RemoveInputSource(is); // cease reading stdin
2097 stdoutClosed = TRUE; // suppress future output
2100 (is->func)(is, is->closure, is->buf, count, count ? errno : 0);
2103 is->unused += count;
2105 /* break input into lines and call the callback function on each
2108 while (p < is->unused) {
2109 q = memchr(p, '\n', is->unused - p);
2110 if (q == NULL) break;
2112 (is->func)(is, is->closure, p, q - p, 0);
2115 /* remember not yet used part of the buffer */
2117 while (p < is->unused) {
2122 /* read maximum length of input buffer and send the whole buffer
2123 * to the callback function
2125 count = read(is->fd, is->buf, INPUT_SOURCE_BUF_SIZE);
2130 (is->func)(is, is->closure, is->buf, count, error);
2132 return True; // Must return true or the watch will be removed
2135 InputSourceRef AddInputSource(pr, lineByLine, func, closure)
2142 GIOChannel *channel;
2143 ChildProc *cp = (ChildProc *) pr;
2145 is = (InputSource *) calloc(1, sizeof(InputSource));
2146 is->lineByLine = lineByLine;
2150 is->fd = fileno(stdin);
2152 is->kind = cp->kind;
2153 is->fd = cp->fdFrom;
2156 is->unused = is->buf;
2160 /* GTK-TODO: will this work on windows?*/
2162 channel = g_io_channel_unix_new(is->fd);
2163 g_io_channel_set_close_on_unref (channel, TRUE);
2164 is->sid = g_io_add_watch(channel, G_IO_IN,(GIOFunc) DoInputCallback, is);
2166 is->closure = closure;
2167 return (InputSourceRef) is;
2172 RemoveInputSource(isr)
2175 InputSource *is = (InputSource *) isr;
2177 if (is->sid == 0) return;
2178 g_source_remove(is->sid);
2185 static Boolean frameWaiting;
2188 FrameAlarm (int sig)
2190 frameWaiting = False;
2191 /* In case System-V style signals. Needed?? */
2192 signal(SIGALRM, FrameAlarm);
2196 FrameDelay (int time)
2198 struct itimerval delay;
2201 frameWaiting = True;
2202 signal(SIGALRM, FrameAlarm);
2203 delay.it_interval.tv_sec =
2204 delay.it_value.tv_sec = time / 1000;
2205 delay.it_interval.tv_usec =
2206 delay.it_value.tv_usec = (time % 1000) * 1000;
2207 setitimer(ITIMER_REAL, &delay, NULL);
2208 while (frameWaiting) pause();
2209 delay.it_interval.tv_sec = delay.it_value.tv_sec = 0;
2210 delay.it_interval.tv_usec = delay.it_value.tv_usec = 0;
2211 setitimer(ITIMER_REAL, &delay, NULL);
2218 FrameDelay (int time)
2221 XSync(xDisplay, False);
2223 // gtk_main_iteration_do(False);
2226 usleep(time * 1000);
2232 LoadLogo (ChessProgramState *cps, int n, Boolean ics)
2234 char buf[MSG_SIZ], *logoName = buf;
2236 if(appData.logo[n][0]) {
2237 logoName = appData.logo[n];
2238 } else if(appData.autoLogo) {
2239 if(ics) { // [HGM] logo: in ICS mode second can be used for ICS
2240 sprintf(buf, "%s/%s.png", appData.logoDir, appData.icsHost);
2241 } else { // engine; look in engine-dir (if any) first
2242 snprintf(buf, MSG_SIZ, "%s/logo.png", appData.directory[n]);
2243 if(appData.directory[n] && appData.directory[n][0]
2244 && strcmp(appData.directory[n], ".") && (f = fopen(buf, "r")) )
2246 else // no engine dir or no logo.png in it: look in logo dir
2247 if(appData.logoDir && appData.logoDir[0])
2248 sprintf(buf, "%s/%s.png", appData.logoDir, cps->tidy);
2252 { ASSIGN(cps->programLogo, logoName); }
2256 UpdateLogos (int displ)
2258 if(optList[W_WHITE-1].handle == NULL) return;
2259 LoadLogo(&first, 0, 0);
2260 LoadLogo(&second, 1, appData.icsActive);
2261 if(displ) DisplayLogos(&optList[W_WHITE-1], &optList[W_BLACK+1]);
2265 void FileNamePopUpWrapper(label, def, filter, proc, pathFlag, openMode, name, fp)
2276 GtkFileFilter *gtkfilter;
2277 GtkFileFilter *gtkfilter_all;
2279 char fileext[10] = "";
2280 char *result = NULL;
2283 /* make a copy of the filter string, so that strtok can work with it*/
2284 cp = strdup(filter);
2286 /* add filters for file extensions */
2287 gtkfilter = gtk_file_filter_new();
2288 gtkfilter_all = gtk_file_filter_new();
2290 /* one filter to show everything */
2291 gtk_file_filter_add_pattern(gtkfilter_all, "*.*");
2292 gtk_file_filter_set_name (gtkfilter_all, "All Files");
2294 /* add filter if present */
2295 result = strtok(cp, space);
2296 while( result != NULL ) {
2297 snprintf(fileext,10,"*%s",result);
2298 result = strtok( NULL, space );
2299 gtk_file_filter_add_pattern(gtkfilter, fileext);
2302 /* second filter to only show what's useful */
2303 gtk_file_filter_set_name (gtkfilter,filter);
2305 if (openMode[0] == 'r')
2307 dialog = gtk_file_chooser_dialog_new (label,
2309 GTK_FILE_CHOOSER_ACTION_OPEN,
2310 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2311 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2316 dialog = gtk_file_chooser_dialog_new (label,
2318 GTK_FILE_CHOOSER_ACTION_SAVE,
2319 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2320 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2322 /* add filename suggestions */
2323 if (strlen(def) > 0 )
2324 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), def);
2326 //gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER (dialog),TRUE);
2330 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter_all);
2331 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2332 /* activate filter */
2333 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER(dialog),gtkfilter);
2335 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
2340 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
2343 f = fopen(filename, openMode);
2346 DisplayError(_("Failed to open file"), errno);
2350 /* TODO add indec */
2352 ASSIGN(*name, filename);
2353 ScheduleDelayedEvent(DelayedLoad, 50);
2358 gtk_widget_destroy (dialog);