79e38d6905c2473ede5c52fadd507fe5445e49cf
[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 Pixmap icons[8]; // [HGM] this front-end array translates back-end icon indicator to handle
94 Widget outputField[2][7]; // [HGM] front-end array to translate output field to window handle
95
96 //extern WindowPlacement wpEvalGraph;
97
98 Position evalGraphX = -1, evalGraphY = -1;
99 Dimension evalGraphW, evalGraphH;
100 Widget evalGraphShell;
101 static int evalGraphDialogUp;
102
103 /* Module variables */
104
105 char *crWhite = "#FFFFB0";
106 char *crBlack = "#AD5D3D";
107 static Display *yDisplay;
108 static Window eGraphWindow;
109
110 static GC pens[6]; // [HGM] put all pens in one array
111 static GC hbrHist[3];
112
113 #if 0
114 static HDC hdcPB = NULL;
115 static HBITMAP hbmPB = NULL;
116 #endif
117
118 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
119 void DrawSegment( int x, int y, int *lastX, int *lastY, int penType )
120 {
121   static int curX, curY;
122
123   if(penType != PEN_NONE)
124     XDrawLine(yDisplay, eGraphWindow, pens[penType], curX, curY, x, y);
125   if(lastX != NULL) { *lastX = curX; *lastY = curY; }
126   curX = x; curY = y;
127 }
128
129 // front-end wrapper for drawing functions to do rectangles
130 void DrawRectangle( int left, int top, int right, int bottom, int side, int style )
131 {
132     XFillRectangle(yDisplay, eGraphWindow, hbrHist[side], left, top, right-left, bottom-top);
133     if(style != FILLED)
134       XDrawRectangle(yDisplay, eGraphWindow, pens[PEN_BLACK], left, top, right-left-1, bottom-top-1);
135 }
136
137 // front-end wrapper for putting text in graph
138 void DrawEvalText(char *buf, int cbBuf, int y)
139 {
140     // the magic constants 7 and 5 should really be derived from the font size somehow
141     XDrawString(yDisplay, eGraphWindow, coordGC, MarginX - 2 - 7*cbBuf, y+5, buf, cbBuf);
142 }
143
144 // front-end
145 static Pixel MakeColor(char *color )
146 {
147     XrmValue vFrom, vTo;
148
149     vFrom.addr = (caddr_t) color;
150     vFrom.size = strlen(color);
151     XtConvert(evalGraphShell, XtRString, &vFrom, XtRPixel, &vTo);
152     // test for NULL?
153
154     return *(Pixel *) vTo.addr;
155 }
156
157 static GC CreateGC(int width, char *fg, char *bg, int style)
158 {
159     XtGCMask value_mask = GCLineWidth | GCLineStyle | GCForeground
160       | GCBackground | GCFunction | GCPlaneMask;
161     XGCValues gc_values;
162
163     gc_values.plane_mask = AllPlanes;
164     gc_values.line_width = width;
165     gc_values.line_style = style;
166     gc_values.function = GXcopy;
167
168     gc_values.foreground = MakeColor(fg);
169     gc_values.background = MakeColor(bg);
170
171     return XtGetGC(evalGraphShell, value_mask, &gc_values);
172 }
173
174 // front-end. Create pens, device context and buffer bitmap for global use, copy result to display
175 // The back-end part n the middle has been taken out and moed to PainEvalGraph()
176 static void DisplayEvalGraph()
177 {
178     int j;
179     int width;
180     int height;
181     Dimension w, h;
182     Arg args[6];
183
184     /* Get client area */
185     j = 0;
186     XtSetArg(args[j], XtNwidth, &w); j++;
187     XtSetArg(args[j], XtNheight, &h); j++;
188     XtGetValues(evalGraphShell, args, j);
189     width = w;
190     height = h;
191
192     /* Create or recreate paint box if needed */
193     if( width != nWidthPB || height != nHeightPB ) {
194
195         nWidthPB = width;
196         nHeightPB = height;
197     }
198
199     // back-end painting; calls back front-end primitives for lines, rectangles and text
200     PaintEvalGraph();
201
202     XSync(yDisplay, False);
203 }
204
205 static void InitializeEvalGraph()
206 {
207   pens[PEN_BLACK]      = CreateGC(1, "black", "black", LineSolid);
208   pens[PEN_DOTTED]     = CreateGC(1, "#A0A0A0", "#A0A0A0", LineOnOffDash);
209   pens[PEN_BLUEDOTTED] = CreateGC(1, "#0000FF", "#0000FF", LineOnOffDash);
210   pens[PEN_BOLD]       = CreateGC(3, crWhite, crWhite, LineSolid);
211   pens[PEN_BOLD+1]     = CreateGC(3, crBlack, crBlack, LineSolid);
212   hbrHist[0] = CreateGC(3, crWhite, crWhite, LineSolid);
213   hbrHist[1] = CreateGC(3, crBlack, crBlack, LineSolid);
214   hbrHist[2] = CreateGC(3, "#E0E0F0", "#E0E0F0", LineSolid);; // background (a bit blueish, for contrst with yellow curve)
215 }
216
217 void EvalClick(widget, unused, event)
218      Widget widget;
219      caddr_t unused;
220      XEvent *event;
221 {
222         if( widget && event->type == ButtonPress ) {
223             int index = GetMoveIndexFromPoint( event->xbutton.x, event->xbutton.y );
224
225             if( index >= 0 && index < currLast ) {
226                 ToNrEvent( index + 1 );
227             }
228         }
229 }
230
231 // This (cloned from EventProc in xboard.c) is needed as event handler, to prevent
232 // the graph being wiped out after covering / uncovering by other windows.
233 void EvalEventProc(widget, unused, event)
234      Widget widget;
235      caddr_t unused;
236      XEvent *event;
237 {
238     if (!XtIsRealized(widget))
239       return;
240
241     switch (event->type) {
242       case Expose:
243         if (event->xexpose.count > 0) return;  /* no clipping is done */
244         DisplayEvalGraph();
245         break;
246       default:
247         return;
248     }
249 }
250 // The following routines are mutated clones of the commentPopUp routines
251
252 Widget EvalGraphCreate(name)
253      char *name;
254 {
255     Arg args[16];
256     Widget shell, layout, form;
257     Dimension bw_width, bw_height;
258     int j;
259
260     // get board width
261     j = 0;
262     XtSetArg(args[j], XtNwidth,  &bw_width);  j++;
263     XtSetArg(args[j], XtNheight, &bw_height);  j++;
264     XtGetValues(boardWidget, args, j);
265
266     // define form within layout within shell.
267     j = 0;
268     XtSetArg(args[j], XtNresizable, True);  j++;
269     shell =
270 #if TOPLEVEL
271      XtCreatePopupShell(name, topLevelShellWidgetClass,
272 #else
273       XtCreatePopupShell(name, transientShellWidgetClass,
274 #endif
275                          shellWidget, args, j);
276     layout =
277       XtCreateManagedWidget(layoutName, formWidgetClass, shell,
278                             layoutArgs, XtNumber(layoutArgs));
279     // divide window vertically into two equal parts, by creating two forms
280     form =
281       XtCreateManagedWidget("form", formWidgetClass, layout,
282                             formArgs, XtNumber(formArgs));
283     // make sure width is known in advance, for better placement of child widgets
284     j = 0;
285     XtSetArg(args[j], XtNwidth,     (XtArgVal) bw_width-16); j++;
286     XtSetArg(args[j], XtNheight,    (XtArgVal) bw_height/4); j++;
287     XtSetValues(shell, args, j);
288
289     XtRealizeWidget(shell);
290
291     if(wpEvalGraph.width > 0) {
292       evalGraphW = wpEvalGraph.width;
293       evalGraphH = wpEvalGraph.height;
294       evalGraphX = wpEvalGraph.x;
295       evalGraphY = wpEvalGraph.y;
296     }
297
298     if (evalGraphX == -1) {
299         int xx, yy;
300         Window junk;
301         evalGraphH = bw_height/4;
302         evalGraphW = bw_width-16;
303
304         XSync(xDisplay, False);
305 #ifdef NOTDEF
306         /* This code seems to tickle an X bug if it is executed too soon
307            after xboard starts up.  The coordinates get transformed as if
308            the main window was positioned at (0, 0).
309            */
310         XtTranslateCoords(shellWidget,
311                           (bw_width - evalGraphW) / 2, 0 - evalGraphH / 2,
312                           &evalGraphX, &evalGraphY);
313 #else  /*!NOTDEF*/
314         XTranslateCoordinates(xDisplay, XtWindow(shellWidget),
315                               RootWindowOfScreen(XtScreen(shellWidget)),
316                               (bw_width - evalGraphW) / 2, 0 - evalGraphH / 2,
317                               &xx, &yy, &junk);
318         evalGraphX = xx;
319         evalGraphY = yy;
320 #endif /*!NOTDEF*/
321         if (evalGraphY < 0) evalGraphY = 0; /*avoid positioning top offscreen*/
322     }
323     j = 0;
324     XtSetArg(args[j], XtNheight, evalGraphH);  j++;
325     XtSetArg(args[j], XtNwidth, evalGraphW);  j++;
326     XtSetArg(args[j], XtNx, evalGraphX);  j++;
327     XtSetArg(args[j], XtNy, evalGraphY);  j++;
328     XtSetValues(shell, args, j);
329
330     yDisplay = XtDisplay(shell);
331     eGraphWindow = XtWindow(form);
332     XtAddEventHandler(form, ExposureMask, False,
333                       (XtEventHandler) EvalEventProc, NULL);
334     XtAddEventHandler(form, ButtonPressMask, False,
335                       (XtEventHandler) EvalClick, NULL);
336
337     return shell;
338 }
339
340 void
341 EvalGraphPopUp()
342 {
343     Arg args[16];
344     int j;
345     static int  needInit = TRUE;
346     static char *title = N_("Evaluation graph");
347
348     if (evalGraphShell == NULL) {
349
350         evalGraphShell =
351           EvalGraphCreate(_(title));
352         XtRealizeWidget(evalGraphShell);
353         CatchDeleteWindow(evalGraphShell, "EvalGraphPopDown");
354         if( needInit ) {
355             InitializeEvalGraph();
356             needInit = FALSE;
357         }
358     } else {
359         j = 0;
360         XtSetArg(args[j], XtNiconName, (XtArgVal) _(title));   j++;
361         XtSetArg(args[j], XtNtitle, (XtArgVal) _(title));      j++;
362         XtSetValues(evalGraphShell, args, j);
363     }
364
365     XtPopup(evalGraphShell, XtGrabNone);
366     XSync(yDisplay, False);
367
368     j=0;
369     XtSetArg(args[j], XtNleftBitmap, xMarkPixmap); j++;
370     XtSetValues(XtNameToWidget(menuBarWidget, "menuView.Show Evaluation Graph"),
371                 args, j);
372
373     evalGraphDialogUp = True;
374 //    ShowThinkingEvent(); // [HGM] thinking: might need to prompt engine for thinking output
375 }
376
377 void EvalGraphPopDown()
378 {
379     Arg args[16];
380     int j;
381
382     if (!evalGraphDialogUp) return;
383     j = 0;
384     XtSetArg(args[j], XtNx, &evalGraphX); j++;
385     XtSetArg(args[j], XtNy, &evalGraphY); j++;
386     XtSetArg(args[j], XtNwidth, &evalGraphW); j++;
387     XtSetArg(args[j], XtNheight, &evalGraphH); j++;
388     XtGetValues(evalGraphShell, args, j);
389     wpEvalGraph.x = evalGraphX - 4;
390     wpEvalGraph.y = evalGraphY - 23;
391     wpEvalGraph.width = evalGraphW;
392     wpEvalGraph.height = evalGraphH;
393     XtPopdown(evalGraphShell);
394     XSync(xDisplay, False);
395     j=0;
396     XtSetArg(args[j], XtNleftBitmap, None); j++;
397     XtSetValues(XtNameToWidget(menuBarWidget, "menuView.Show Evaluation Graph"),
398                 args, j);
399
400     evalGraphDialogUp = False;
401 //    ShowThinkingEvent(); // [HGM] thinking: might need to shut off thinking output
402 }
403
404 Boolean EvalGraphIsUp()
405 {
406     return evalGraphDialogUp;
407 }
408
409 int EvalGraphDialogExists()
410 {
411     return evalGraphShell != NULL;
412 }
413
414 void
415 EvalGraphProc(w, event, prms, nprms)
416      Widget w;
417      XEvent *event;
418      String *prms;
419      Cardinal *nprms;
420 {
421   if (evalGraphDialogUp) {
422     EvalGraphPopDown();
423   } else {
424     EvalGraphPopUp();
425   }
426 }
427 // This function is the interface to the back-end. It is currently called through the front-end,
428 // though, where it shares the HistorySet() wrapper with MoveHistorySet(). Once all front-ends
429 // support the eval graph, it would be more logical to call it directly from the back-end.
430 void EvalGraphSet( int first, int last, int current, ChessProgramStats_Move * pvInfo )
431 {
432     /* [AS] Danger! For now we rely on the pvInfo parameter being a static variable! */
433
434     currFirst = first;
435     currLast = last;
436     currCurrent = current;
437     currPvInfo = pvInfo;
438
439     if( evalGraphShell ) {
440         DisplayEvalGraph();
441     }
442 }