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