Redo Eval Graph drawing with cairo
[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, 2012 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 <cairo/cairo.h>
71 #include <cairo/cairo-xlib.h>
72
73 #include "common.h"
74 #include "frontend.h"
75 #include "backend.h"
76 #include "dialogs.h"
77 #include "menus.h"
78 #include "xboard.h"
79 #include "evalgraph.h"
80 #include "gettext.h"
81
82 #ifdef ENABLE_NLS
83 # define  _(s) gettext (s)
84 # define N_(s) gettext_noop (s)
85 #else
86 # define  _(s) (s)
87 # define N_(s)  s
88 #endif
89
90 #include <X11/xpm.h>
91
92 #ifdef SNAP
93 #include "wsnap.h"
94 #endif
95
96 #define _LL_ 100
97
98 Pixmap icons[8]; // [HGM] this front-end array translates back-end icon indicator to handle
99 Widget outputField[2][7]; // [HGM] front-end array to translate output field to window handle
100 static char *title = N_("Evaluation graph");
101
102 //extern WindowPlacement wpEvalGraph;
103
104 Position evalGraphX = -1, evalGraphY = -1;
105 Dimension evalGraphW, evalGraphH;
106
107 /* Module variables */
108
109 char *crWhite = "#FFFFB0";
110 char *crBlack = "#AD5D3D";
111 static Window eGraphWindow;
112 static cairo_surface_t *cs;
113
114 static Option *EvalCallback P((int button, int x, int y));
115
116 static float
117 Color (char *col, int n)
118 {
119   int c;
120   sscanf(col, "#%x", &c);
121   c = c >> 4*n & 255;
122   return c/255.;
123 }
124
125 static void
126 SetPen(cairo_t *cr, float w, char *col, int dash) {
127   static const double dotted[] = {4.0, 4.0};
128   static int len  = sizeof(dotted) / sizeof(dotted[0]);
129   cairo_set_line_width (cr, w);
130   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
131   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
132 }
133
134 static void
135 ChoosePen(cairo_t *cr, int i)
136 {
137   switch(i) {
138     case PEN_BLACK:
139       SetPen(cr, 1.0, "#000000", 0);
140       break;
141     case PEN_DOTTED:
142       SetPen(cr, 1.0, "#A0A0A0", 1);
143       break;
144     case PEN_BLUEDOTTED:
145       SetPen(cr, 0.5, "#0000FF", 1);
146       break;
147     case PEN_BOLDWHITE:
148       SetPen(cr, 3.0, crWhite, 0);
149       break;
150     case PEN_BOLDBLACK:
151       SetPen(cr, 3.0, crBlack, 0);
152       break;
153     case PEN_BACKGD:
154       SetPen(cr, 3.0, "#E0E0F0", 0);
155       break;
156   }
157 }
158
159 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
160 void
161 DrawSegment (int x, int y, int *lastX, int *lastY, enum PEN penType)
162 {
163   static int curX, curY;
164
165   if(penType != PEN_NONE) {
166     cairo_t *cr = cairo_create(cs);
167     cairo_move_to (cr, curX, curY);
168     cairo_line_to (cr, x,y);
169     cairo_close_path (cr);
170     ChoosePen(cr, penType);
171     cairo_stroke (cr);
172     cairo_destroy (cr);
173   }
174
175   if(lastX != NULL) { *lastX = curX; *lastY = curY; }
176   curX = x; curY = y;
177 }
178
179 // front-end wrapper for drawing functions to do rectangles
180 void
181 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
182 {
183   cairo_t *cr;
184
185   cr = cairo_create (cs);
186   cairo_rectangle (cr, left, top, right-left, bottom-top);
187   switch(side)
188     {
189     case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
190     case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
191     case 2: ChoosePen(cr, PEN_BACKGD); break;
192     }
193   cairo_fill (cr);
194
195   if(style != FILLED)
196     {
197       cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
198       ChoosePen(cr, PEN_BLACK);
199       cairo_stroke (cr);
200     }
201
202   cairo_destroy(cr);
203 }
204
205 // front-end wrapper for putting text in graph
206 void
207 DrawEvalText (char *buf, int cbBuf, int y)
208 {
209     // the magic constants 7 and 5 should really be derived from the font size somehow
210   cairo_text_extents_t extents;
211   cairo_t *cr = cairo_create(cs);
212
213   /* GTK-TODO this has to go into the font-selection */
214   cairo_select_font_face (cr, "Sans",
215                           CAIRO_FONT_SLANT_NORMAL,
216                           CAIRO_FONT_WEIGHT_NORMAL);
217   cairo_set_font_size (cr, 12.0);
218
219
220   cairo_text_extents (cr, buf, &extents);
221
222   cairo_move_to (cr, MarginX - 2 - 7*cbBuf, y+5);
223   cairo_text_path (cr, buf);
224   cairo_set_source_rgb (cr, 0.0, 0.0, 0);
225   cairo_fill_preserve (cr);
226   cairo_set_source_rgb (cr, 0, 1.0, 0);
227   cairo_set_line_width (cr, 0.1);
228   cairo_stroke (cr);
229
230   /* free memory */
231   cairo_destroy (cr);
232 }
233
234 static int initDone = FALSE;
235
236 static void
237 InitializeEvalGraph (Option *opt, int w, int h)
238 {
239   eGraphWindow = XtWindow(opt->handle);
240
241   if(w == 0) {
242     Arg args[10];
243     XtSetArg(args[0], XtNwidth, &evalGraphW);
244     XtSetArg(args[1], XtNheight, &evalGraphH);
245     XtGetValues(opt->handle, args, 2);
246     nWidthPB = evalGraphW; nHeightPB = evalGraphH;
247   } else nWidthPB = w, nHeightPB = h;
248
249   if(cs) cairo_surface_destroy(cs);
250   cs=cairo_xlib_surface_create(xDisplay, eGraphWindow, DefaultVisual(xDisplay, 0), nWidthPB, nHeightPB);
251
252   initDone = TRUE;
253 }
254
255 // The following stuff is really back-end (but too little to bother with a separate file)
256
257 static void
258 DisplayEvalGraph ()
259 {   // back-end painting; calls back front-end primitives for lines, rectangles and text
260     char *t = MakeEvalTitle(_(title));
261     if(t != title && nWidthPB < 340) t = MakeEvalTitle(nWidthPB < 240 ? "" : _("Eval"));
262     PaintEvalGraph();
263     SetDialogTitle(EvalGraphDlg, t);
264 }
265
266 static void
267 EvalClick (int x, int y)
268 {
269     int index = GetMoveIndexFromPoint( x, y );
270
271     if( index >= 0 && index < currLast ) ToNrEvent( index + 1 );
272 }
273
274 static Option graphOptions[] = {
275 { 150, 0x9C, 300, NULL, (void*) &EvalCallback, NULL, NULL, Graph , "" },
276 { 0, 2, 0, NULL, NULL, "", NULL, EndMark , "" }
277 };
278
279 static Option *
280 EvalCallback (int button, int x, int y)
281 {
282     if(!initDone) return NULL;
283
284     switch(button) {
285         case 10: // expose event
286             /* Create or recreate paint box if needed */
287             if(x != nWidthPB || y != nHeightPB) {
288                 InitializeEvalGraph(&graphOptions[0], x, y);
289             }
290             nWidthPB = x;
291             nHeightPB = y;
292             DisplayEvalGraph();
293             break;
294         case 1: EvalClick(x, y); // left button
295         default: break; // other buttons ignored
296     }
297     return NULL; // no context menu!
298 }
299
300 void
301 EvalGraphPopUp ()
302 {
303     if (GenericPopUp(graphOptions, _(title), EvalGraphDlg, BoardWindow, NONMODAL, 1)) {
304         InitializeEvalGraph(&graphOptions[0], 0, 0); // first time: add callbacks and initialize pens
305     } else {
306         SetDialogTitle(EvalGraphDlg, _(title));
307         SetIconName(EvalGraphDlg, _(title));
308     }
309
310     MarkMenu("View.EvaluationGraph", EvalGraphDlg);
311
312 //    ShowThinkingEvent(); // [HGM] thinking: might need to prompt engine for thinking output
313 }
314
315 void
316 EvalGraphPopDown ()
317 {
318     PopDown(EvalGraphDlg);
319
320 //    ShowThinkingEvent(); // [HGM] thinking: might need to shut off thinking output
321 }
322
323 Boolean
324 EvalGraphIsUp ()
325 {
326     return shellUp[EvalGraphDlg];
327 }
328
329 int
330 EvalGraphDialogExists ()
331 {
332     return DialogExists(EvalGraphDlg);
333 }
334
335 void
336 EvalGraphProc ()
337 {
338   if (!PopDown(EvalGraphDlg)) EvalGraphPopUp();
339 }
340
341 // This function is the interface to the back-end.
342
343 void
344 EvalGraphSet (int first, int last, int current, ChessProgramStats_Move * pvInfo)
345 {
346     /* [AS] Danger! For now we rely on the pvInfo parameter being a static variable! */
347
348     currFirst = first;
349     currLast = last;
350     currCurrent = current;
351     currPvInfo = pvInfo;
352
353     if( DialogExists(EvalGraphDlg) ) {
354         DisplayEvalGraph();
355     }
356 }
357