Updated copyright notice to 2011
[xboard.git] / xevalgraph.c
1 /*
2  * Evaluation graph
3  *
4  * Author: Alessandro Scotti (Dec 2005)
5  * Translated to X by H.G.Muller (Nov 2009)
6  *
7  * Copyright 2005 Alessandro Scotti
8  *
9  * Enhancements Copyright 2009, 2010, 2011 Free Software Foundation, Inc.
10  *
11  * ------------------------------------------------------------------------
12  *
13  * GNU XBoard is free software: you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation, either version 3 of the License, or (at
16  * your option) any later version.
17  *
18  * GNU XBoard is distributed in the hope that it will be useful, but
19  * WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21  * General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program. If not, see http://www.gnu.org/licenses/.
25  *
26  * ------------------------------------------------------------------------
27  ** See the file ChangeLog for a revision history.  */
28
29 #include "config.h"
30
31 #include <stdio.h>
32 #include <ctype.h>
33 #include <errno.h>
34 #include <sys/types.h>
35
36 #if STDC_HEADERS
37 # include <stdlib.h>
38 # include <string.h>
39 #else /* not STDC_HEADERS */
40 extern char *getenv();
41 # if HAVE_STRING_H
42 #  include <string.h>
43 # else /* not HAVE_STRING_H */
44 #  include <strings.h>
45 # endif /* not HAVE_STRING_H */
46 #endif /* not STDC_HEADERS */
47
48 #if HAVE_UNISTD_H
49 # include <unistd.h>
50 #endif
51
52 #include <X11/Intrinsic.h>
53 #include <X11/StringDefs.h>
54 #include <X11/Shell.h>
55 #include <X11/Xaw/Dialog.h>
56 #include <X11/Xaw/Form.h>
57 #include <X11/Xaw/List.h>
58 #include <X11/Xaw/Label.h>
59 #include <X11/Xaw/SimpleMenu.h>
60 #include <X11/Xaw/SmeBSB.h>
61 #include <X11/Xaw/SmeLine.h>
62 #include <X11/Xaw/Box.h>
63 #include <X11/Xaw/Paned.h>
64 #include <X11/Xaw/MenuButton.h>
65 #include <X11/cursorfont.h>
66 #include <X11/Xaw/Text.h>
67 #include <X11/Xaw/AsciiText.h>
68 #include <X11/Xaw/Viewport.h>
69
70 #include "common.h"
71 #include "frontend.h"
72 #include "backend.h"
73 #include "xboard.h"
74 #include "evalgraph.h"
75 #include "gettext.h"
76
77 #ifdef ENABLE_NLS
78 # define  _(s) gettext (s)
79 # define N_(s) gettext_noop (s)
80 #else
81 # define  _(s) (s)
82 # define N_(s)  s
83 #endif
84
85 #include <X11/xpm.h>
86
87 #ifdef SNAP
88 #include "wsnap.h"
89 #endif
90
91 #define _LL_ 100
92
93 // imports from xboard.c
94 extern Widget formWidget, shellWidget, boardWidget, menuBarWidget;
95 extern Display *xDisplay;
96 extern Window xBoardWindow;
97 extern int squareSize;
98 extern Pixmap xMarkPixmap, wIconPixmap, bIconPixmap;
99 extern char *layoutName;
100
101 Pixmap icons[8]; // [HGM] this front-end array translates back-end icon indicator to handle
102 Widget outputField[2][7]; // [HGM] front-end array to translate output field to window handle
103
104 /* Imports from backend.c */
105
106 /* Imports from xboard.c */
107 extern Arg layoutArgs[2], formArgs[2], messageArgs[4];
108 extern GC coordGC;
109
110 //extern WindowPlacement wpEvalGraph;
111
112 Position evalGraphX = -1, evalGraphY = -1;
113 Dimension evalGraphW, evalGraphH;
114 Widget evalGraphShell;
115 static int evalGraphDialogUp;
116
117 /* Module variables */
118
119 char *crWhite = "#FFFFB0";
120 char *crBlack = "#AD5D3D";
121 static Display *yDisplay;
122 static Window eGraphWindow;
123
124 static GC pens[6]; // [HGM] put all pens in one array
125 static GC hbrHist[3];
126
127 #if 0
128 static HDC hdcPB = NULL;
129 static HBITMAP hbmPB = NULL;
130 #endif
131
132 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
133 void DrawSegment( int x, int y, int *lastX, int *lastY, int penType )
134 {
135   static int curX, curY;
136
137   if(penType != PEN_NONE)
138     XDrawLine(yDisplay, eGraphWindow, pens[penType], curX, curY, x, y);
139   if(lastX != NULL) { *lastX = curX; *lastY = curY; }
140   curX = x; curY = y;
141 }
142
143 // front-end wrapper for drawing functions to do rectangles
144 void DrawRectangle( int left, int top, int right, int bottom, int side, int style )
145 {
146     XFillRectangle(yDisplay, eGraphWindow, hbrHist[side], left, top, right-left, bottom-top);
147     if(style != FILLED)
148       XDrawRectangle(yDisplay, eGraphWindow, pens[PEN_BLACK], left, top, right-left-1, bottom-top-1);
149 }
150
151 // front-end wrapper for putting text in graph
152 void DrawEvalText(char *buf, int cbBuf, int y)
153 {
154     // the magic constants 7 and 5 should really be derived from the font size somehow
155     XDrawString(yDisplay, eGraphWindow, coordGC, MarginX - 2 - 7*cbBuf, y+5, buf, cbBuf);
156 }
157
158 // front-end
159 static Pixel MakeColor(char *color )
160 {
161     XrmValue vFrom, vTo;
162
163     vFrom.addr = (caddr_t) color;
164     vFrom.size = strlen(color);
165     XtConvert(evalGraphShell, XtRString, &vFrom, XtRPixel, &vTo);
166     // test for NULL?
167
168     return *(Pixel *) vTo.addr;
169 }
170
171 static GC CreateGC(int width, char *fg, char *bg, int style)
172 {
173     XtGCMask value_mask = GCLineWidth | GCLineStyle | GCForeground
174       | GCBackground | GCFunction | GCPlaneMask;
175     XGCValues gc_values;
176
177     gc_values.plane_mask = AllPlanes;
178     gc_values.line_width = width;
179     gc_values.line_style = style;
180     gc_values.function = GXcopy;
181
182     gc_values.foreground = MakeColor(fg);
183     gc_values.background = MakeColor(bg);
184
185     return XtGetGC(evalGraphShell, value_mask, &gc_values);
186 }
187
188 // front-end. Create pens, device context and buffer bitmap for global use, copy result to display
189 // The back-end part n the middle has been taken out and moed to PainEvalGraph()
190 static void DisplayEvalGraph()
191 {
192     int j;
193     int width;
194     int height;
195     Dimension w, h;
196     Arg args[6];
197
198     /* Get client area */
199     j = 0;
200     XtSetArg(args[j], XtNwidth, &w); j++;
201     XtSetArg(args[j], XtNheight, &h); j++;
202     XtGetValues(evalGraphShell, args, j);
203     width = w;
204     height = h;
205
206     /* Create or recreate paint box if needed */
207     if( width != nWidthPB || height != nHeightPB ) {
208
209         nWidthPB = width;
210         nHeightPB = height;
211     }
212
213     // back-end painting; calls back front-end primitives for lines, rectangles and text
214     PaintEvalGraph();
215
216     XSync(yDisplay, False);
217 }
218
219 static void InitializeEvalGraph()
220 {
221   pens[PEN_BLACK]      = CreateGC(1, "black", "black", LineSolid);
222   pens[PEN_DOTTED]     = CreateGC(1, "#A0A0A0", "#A0A0A0", LineOnOffDash);
223   pens[PEN_BLUEDOTTED] = CreateGC(1, "#0000FF", "#0000FF", LineOnOffDash);
224   pens[PEN_BOLD]       = CreateGC(3, crWhite, crWhite, LineSolid);
225   pens[PEN_BOLD+1]     = CreateGC(3, crBlack, crBlack, LineSolid);
226   hbrHist[0] = CreateGC(3, crWhite, crWhite, LineSolid);
227   hbrHist[1] = CreateGC(3, crBlack, crBlack, LineSolid);
228   hbrHist[2] = CreateGC(3, "#E0E0F0", "#E0E0F0", LineSolid);; // background (a bit blueish, for contrst with yellow curve)
229 }
230
231 void EvalClick(widget, unused, event)
232      Widget widget;
233      caddr_t unused;
234      XEvent *event;
235 {
236         if( widget && event->type == ButtonPress ) {
237             int index = GetMoveIndexFromPoint( event->xbutton.x, event->xbutton.y );
238
239             if( index >= 0 && index < currLast ) {
240                 ToNrEvent( index + 1 );
241             }
242         }
243 }
244
245 // This (cloned from EventProc in xboard.c) is needed as event handler, to prevent
246 // the graph being wiped out after covering / uncovering by other windows.
247 void EvalEventProc(widget, unused, event)
248      Widget widget;
249      caddr_t unused;
250      XEvent *event;
251 {
252     if (!XtIsRealized(widget))
253       return;
254
255     switch (event->type) {
256       case Expose:
257         if (event->xexpose.count > 0) return;  /* no clipping is done */
258         DisplayEvalGraph();
259         break;
260       default:
261         return;
262     }
263 }
264 // The following routines are mutated clones of the commentPopUp routines
265
266 Widget EvalGraphCreate(name)
267      char *name;
268 {
269     Arg args[16];
270     Widget shell, layout, form;
271     Dimension bw_width, bw_height;
272     int j;
273
274     // get board width
275     j = 0;
276     XtSetArg(args[j], XtNwidth,  &bw_width);  j++;
277     XtSetArg(args[j], XtNheight, &bw_height);  j++;
278     XtGetValues(boardWidget, args, j);
279
280     // define form within layout within shell.
281     j = 0;
282     XtSetArg(args[j], XtNresizable, True);  j++;
283     shell =
284 #if TOPLEVEL
285      XtCreatePopupShell(name, topLevelShellWidgetClass,
286 #else
287       XtCreatePopupShell(name, transientShellWidgetClass,
288 #endif
289                          shellWidget, args, j);
290     layout =
291       XtCreateManagedWidget(layoutName, formWidgetClass, shell,
292                             layoutArgs, XtNumber(layoutArgs));
293     // divide window vertically into two equal parts, by creating two forms
294     form =
295       XtCreateManagedWidget("form", formWidgetClass, layout,
296                             formArgs, XtNumber(formArgs));
297     // make sure width is known in advance, for better placement of child widgets
298     j = 0;
299     XtSetArg(args[j], XtNwidth,     (XtArgVal) bw_width-16); j++;
300     XtSetArg(args[j], XtNheight,    (XtArgVal) bw_height/4); j++;
301     XtSetValues(shell, args, j);
302
303     XtRealizeWidget(shell);
304
305     if(wpEvalGraph.width > 0) {
306       evalGraphW = wpEvalGraph.width;
307       evalGraphH = wpEvalGraph.height;
308       evalGraphX = wpEvalGraph.x;
309       evalGraphY = wpEvalGraph.y;
310     }
311
312     if (evalGraphX == -1) {
313         int xx, yy;
314         Window junk;
315         evalGraphH = bw_height/4;
316         evalGraphW = bw_width-16;
317
318         XSync(xDisplay, False);
319 #ifdef NOTDEF
320         /* This code seems to tickle an X bug if it is executed too soon
321            after xboard starts up.  The coordinates get transformed as if
322            the main window was positioned at (0, 0).
323            */
324         XtTranslateCoords(shellWidget,
325                           (bw_width - evalGraphW) / 2, 0 - evalGraphH / 2,
326                           &evalGraphX, &evalGraphY);
327 #else  /*!NOTDEF*/
328         XTranslateCoordinates(xDisplay, XtWindow(shellWidget),
329                               RootWindowOfScreen(XtScreen(shellWidget)),
330                               (bw_width - evalGraphW) / 2, 0 - evalGraphH / 2,
331                               &xx, &yy, &junk);
332         evalGraphX = xx;
333         evalGraphY = yy;
334 #endif /*!NOTDEF*/
335         if (evalGraphY < 0) evalGraphY = 0; /*avoid positioning top offscreen*/
336     }
337     j = 0;
338     XtSetArg(args[j], XtNheight, evalGraphH);  j++;
339     XtSetArg(args[j], XtNwidth, evalGraphW);  j++;
340     XtSetArg(args[j], XtNx, evalGraphX);  j++;
341     XtSetArg(args[j], XtNy, evalGraphY);  j++;
342     XtSetValues(shell, args, j);
343
344     yDisplay = XtDisplay(shell);
345     eGraphWindow = XtWindow(form);
346     XtAddEventHandler(form, ExposureMask, False,
347                       (XtEventHandler) EvalEventProc, NULL);
348     XtAddEventHandler(form, ButtonPressMask, False,
349                       (XtEventHandler) EvalClick, NULL);
350
351     return shell;
352 }
353
354 void
355 EvalGraphPopUp()
356 {
357     Arg args[16];
358     int j;
359     static int  needInit = TRUE;
360     static char *title = _("Evaluation graph");
361
362     if (evalGraphShell == NULL) {
363
364         evalGraphShell =
365           EvalGraphCreate(title);
366         XtRealizeWidget(evalGraphShell);
367         CatchDeleteWindow(evalGraphShell, "EvalGraphPopDown");
368         if( needInit ) {
369             InitializeEvalGraph();
370             needInit = FALSE;
371         }
372     } else {
373         j = 0;
374         XtSetArg(args[j], XtNiconName, (XtArgVal) title);   j++;
375         XtSetArg(args[j], XtNtitle, (XtArgVal) title);      j++;
376         XtSetValues(evalGraphShell, args, j);
377     }
378
379     XtPopup(evalGraphShell, XtGrabNone);
380     XSync(yDisplay, False);
381
382     j=0;
383     XtSetArg(args[j], XtNleftBitmap, xMarkPixmap); j++;
384     XtSetValues(XtNameToWidget(menuBarWidget, "menuView.Show Evaluation Graph"),
385                 args, j);
386
387     evalGraphDialogUp = True;
388 //    ShowThinkingEvent(); // [HGM] thinking: might need to prompt engine for thinking output
389 }
390
391 void EvalGraphPopDown()
392 {
393     Arg args[16];
394     int j;
395
396     if (!evalGraphDialogUp) return;
397     j = 0;
398     XtSetArg(args[j], XtNx, &evalGraphX); j++;
399     XtSetArg(args[j], XtNy, &evalGraphY); j++;
400     XtSetArg(args[j], XtNwidth, &evalGraphW); j++;
401     XtSetArg(args[j], XtNheight, &evalGraphH); j++;
402     XtGetValues(evalGraphShell, args, j);
403     wpEvalGraph.x = evalGraphX - 4;
404     wpEvalGraph.y = evalGraphY - 23;
405     wpEvalGraph.width = evalGraphW;
406     wpEvalGraph.height = evalGraphH;
407     XtPopdown(evalGraphShell);
408     XSync(xDisplay, False);
409     j=0;
410     XtSetArg(args[j], XtNleftBitmap, None); j++;
411     XtSetValues(XtNameToWidget(menuBarWidget, "menuView.Show Evaluation Graph"),
412                 args, j);
413
414     evalGraphDialogUp = False;
415 //    ShowThinkingEvent(); // [HGM] thinking: might need to shut off thinking output
416 }
417
418 Boolean EvalGraphIsUp()
419 {
420     return evalGraphDialogUp;
421 }
422
423 int EvalGraphDialogExists()
424 {
425     return evalGraphShell != NULL;
426 }
427
428 void
429 EvalGraphProc(w, event, prms, nprms)
430      Widget w;
431      XEvent *event;
432      String *prms;
433      Cardinal *nprms;
434 {
435   if (evalGraphDialogUp) {
436     EvalGraphPopDown();
437   } else {
438     EvalGraphPopUp();
439   }
440 }
441 // This function is the interface to the back-end. It is currently called through the front-end,
442 // though, where it shares the HistorySet() wrapper with MoveHistorySet(). Once all front-ends
443 // support the eval graph, it would be more logical to call it directly from the back-end.
444 void EvalGraphSet( int first, int last, int current, ChessProgramStats_Move * pvInfo )
445 {
446     /* [AS] Danger! For now we rely on the pvInfo parameter being a static variable! */
447
448     currFirst = first;
449     currLast = last;
450     currCurrent = current;
451     currPvInfo = pvInfo;
452
453     if( evalGraphShell ) {
454         DisplayEvalGraph();
455     }
456 }