Fix vertical chaining of Buttons and browser ListBoxes
[xboard.git] / evalgraph.c
1 /*
2  * evalgraph.c - Evaluation graph back-end part
3  *
4  * Author: Alessandro Scotti (Dec 2005)
5  *
6  * Copyright 2005 Alessandro Scotti
7  *
8  * Enhancments Copyright 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
9  *
10  * ------------------------------------------------------------------------
11  *
12  * GNU XBoard is free software: you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation, either version 3 of the License, or (at
15  * your option) any later version.
16  *
17  * GNU XBoard is distributed in the hope that it will be useful, but
18  * WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20  * General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program. If not, see http://www.gnu.org/licenses/.  *
24  *
25  *------------------------------------------------------------------------
26  ** See the file ChangeLog for a revision history.  */
27
28 // code refactored by HGM to obtain front-end / back-end separation
29
30 #include "config.h"
31
32 #include <stdio.h>
33
34 #if STDC_HEADERS
35 # include <stdlib.h>
36 # include <string.h>
37 #else /* not STDC_HEADERS */
38 # if HAVE_STRING_H
39 #  include <string.h>
40 # else /* not HAVE_STRING_H */
41 #  include <strings.h>
42 # endif /* not HAVE_STRING_H */
43 #endif /* not STDC_HEADERS */
44
45 #include "common.h"
46 #include "frontend.h"
47 #include "backend.h"
48 #include "evalgraph.h"
49
50 /* Module globals */
51 ChessProgramStats_Move * currPvInfo;
52 int currFirst = 0;
53 int currLast = 0;
54 int currCurrent = -1;
55 int range = 1;
56
57 int nWidthPB = 0;
58 int nHeightPB = 0;
59
60 int MarginX = 18;
61 int MarginW = 4;
62 int MarginH = 4;
63
64 // back-end
65 static void
66 DrawLine (int x1, int y1, int x2, int y2, int penType)
67 {
68     DrawSegment( x1, y1, NULL, NULL, PEN_NONE );
69     DrawSegment( x2, y2, NULL, NULL, penType );
70 }
71
72 // back-end
73 static void
74 DrawLineEx (int x1, int y1, int x2, int y2, int penType)
75 {
76     int savX, savY;
77     DrawSegment( x1, y1, &savX, &savY, PEN_NONE );
78     DrawSegment( x2, y2, NULL, NULL, penType );
79     DrawSegment( savX, savY, NULL, NULL, PEN_NONE );
80 }
81
82 // back-end
83 static int
84 GetPvScore (int index)
85 {
86     int score = currPvInfo[ index ].score;
87
88     if( index & 1 ) score = -score; /* Flip score for black */
89
90     return score;
91 }
92
93 char *
94 MakeEvalTitle (char *title)
95 {
96     int score, depth;
97     static char buf[MSG_SIZ];
98
99     if( currCurrent <0 ) return title; // currCurrent = -1 crashed WB on start without ini file!
100     score = currPvInfo[ currCurrent ].score;
101     depth = currPvInfo[ currCurrent ].depth;
102
103     if( depth <=0 ) return title;
104     if( currCurrent & 1 ) score = -score; /* Flip score for black */
105     snprintf(buf, MSG_SIZ, "%s {%d: %s%.2f/%-2d %d}", title, currCurrent/2+1, 
106                                 score>0 ? "+" : " ", score/100., depth, (currPvInfo[currCurrent].time+50)/100);
107
108     return buf;
109 }
110
111 // back-end
112 /*
113     For a centipawn value, this function returns the height of the corresponding
114     histogram, centered on the reference axis.
115
116     Note: height can be negative!
117 */
118 static int
119 GetValueY (int value)
120 {
121     if( value < -range*700 ) value = -range*700;
122     if( value > +range*700 ) value = +range*700;
123     if(value > 100*range)  value += appData.zoom * 100 - 100*range; else
124     if(value < -100*range) value -= appData.zoom * 100 - 100*range; else
125         value *= appData.zoom;
126     return (nHeightPB / 2) - (int)(value * (nHeightPB - 2*MarginH) / ((1200. + 200.*appData.zoom)*range));
127 }
128
129 // the brush selection is made part of the DrawLine, by passing a style argument
130 // the wrapper for doing the text output makes this back-end
131 static void
132 DrawAxisSegmentHoriz (int value, Boolean drawValue)
133 {
134     int y = GetValueY( range*value*100 );
135
136     if( drawValue ) {
137         char buf[MSG_SIZ], *b = buf;
138
139         if( value > 0 ) *b++ = '+';
140         sprintf(b, "%d", range*value);
141
142         DrawEvalText(buf, strlen(buf), y);
143     }
144     // [HGM] counts on DrawEvalText to have select transparent background for dotted line!
145     DrawLine( MarginX, y, MarginX + MarginW, y, PEN_BLACK ); // Y-axis tick marks
146     DrawLine( MarginX + MarginW, y, nWidthPB - MarginW, y, PEN_DOTTED ); // hor grid
147 }
148
149 // The DrawLines again must select their own brush.
150 // the initial brush selection is useless? BkMode needed for dotted line and text
151 static void
152 DrawAxis ()
153 {
154     int cy = nHeightPB / 2, space = nHeightPB/(6 + appData.zoom);
155     
156     DrawAxisSegmentHoriz( +5, TRUE );
157     DrawAxisSegmentHoriz( +3, space >= 20 );
158     DrawAxisSegmentHoriz( +1, space >= 20 && space*appData.zoom >= 40 );
159     DrawAxisSegmentHoriz(  0, TRUE );
160     DrawAxisSegmentHoriz( -1, space >= 20 && space*appData.zoom >= 40 );
161     DrawAxisSegmentHoriz( -3, space >= 20 );
162     DrawAxisSegmentHoriz( -5, TRUE );
163
164     DrawLine( MarginX + MarginW, cy, nWidthPB - MarginW, cy, PEN_BLACK ); // x-axis
165     DrawLine( MarginX + MarginW, MarginH, MarginX + MarginW, nHeightPB - MarginH, PEN_BLACK ); // y-axis
166 }
167
168 // back-end
169 static void
170 DrawHistogram (int x, int y, int width, int value, int side)
171 {
172     int left, top, right, bottom;
173
174     if( value > -appData.evalThreshold*range && value < +appData.evalThreshold*range ) return;
175
176     left = x;
177     right = left + width + 1;
178
179     if( value > 0 ) {
180         top = GetValueY( value );
181         bottom = y+1;
182     }
183     else {
184         top = y;
185         bottom = GetValueY( value ) + 1;
186     }
187
188
189     if( width == MIN_HIST_WIDTH ) {
190         right--;
191         DrawRectangle( left, top, right, bottom, side, FILLED );
192     }
193     else {
194         DrawRectangle( left, top, right, bottom, side, OPEN );
195     }
196 }
197
198 // back-end
199 static void
200 DrawSeparator (int index, int x)
201 {
202     if( index > 0 ) {
203         if( index == currCurrent ) {
204             DrawLineEx( x, MarginH, x, nHeightPB - MarginH, PEN_BLUEDOTTED );
205         }
206         else if( (index % 20) == 0 ) {
207             DrawLineEx( x, MarginH, x, nHeightPB - MarginH, PEN_DOTTED );
208         }
209     }
210 }
211
212 // made back-end by replacing MoveToEx and LineTo by DrawSegment
213 /* Actually draw histogram as a diagram, cause there's too much data */
214 static void
215 DrawHistogramAsDiagram (int cy, int paint_width, int hist_count)
216 {
217     double step;
218     int i;
219
220     /* Rescale the graph every few moves (as opposed to every move) */
221     hist_count -= hist_count % 8;
222     hist_count += 8;
223     hist_count /= 2;
224
225     step = (double) paint_width / (hist_count + 1);
226
227     for( i=0; i<2; i++ ) {
228         int index = currFirst;
229         int side = (currCurrent + i + 1) & 1; /* Draw current side last */
230         double x = MarginX + MarginW;
231
232         if( (index & 1) != side ) {
233             x += step / 2;
234             index++;
235         }
236
237         DrawSegment( (int) x, cy, NULL, NULL, PEN_NONE );
238
239         index += 2;
240
241         while( index < currLast ) {
242             x += step;
243
244             DrawSeparator( index, (int) x );
245
246             /* Extend line up to current point */
247             if( currPvInfo[index].depth > 0 ) {
248                 DrawSegment((int) x, GetValueY( GetPvScore(index) ), NULL, NULL, PEN_BOLD + side );
249             }
250
251             index += 2;
252         }
253     }
254 }
255
256 // back-end, delete pen selection
257 static void
258 DrawHistogramFull (int cy, int hist_width, int hist_count)
259 {
260     int i;
261
262 //    SelectObject( hdcPB, GetStockObject(BLACK_PEN) );
263
264     for( i=0; i<hist_count; i++ ) {
265         int index = currFirst + i;
266         int x = MarginX + MarginW + index * hist_width;
267
268         /* Draw a separator every 10 moves */
269         DrawSeparator( index, x );
270
271         /* Draw histogram */
272         if( currPvInfo[i].depth > 0 ) {
273             DrawHistogram( x, cy, hist_width, GetPvScore(index), index & 1 );
274         }
275     }
276 }
277
278 typedef struct {
279     int cy;
280     int hist_width;
281     int hist_count;
282     int paint_width;
283 } VisualizationData;
284
285 // back-end
286 static Boolean
287 InitVisualization (VisualizationData *vd)
288 {
289     Boolean result = FALSE;
290
291     vd->cy = nHeightPB / 2;
292     vd->hist_width = MIN_HIST_WIDTH;
293     vd->hist_count = currLast - currFirst;
294     vd->paint_width = nWidthPB - MarginX - 2*MarginW;
295
296     if( vd->hist_count > 0 ) {
297         result = TRUE;
298
299         /* Compute width */
300         vd->hist_width = vd->paint_width / vd->hist_count;
301
302         if( vd->hist_width > MAX_HIST_WIDTH ) vd->hist_width = MAX_HIST_WIDTH;
303
304         vd->hist_width -= vd->hist_width % 2;
305     }
306
307     return result;
308 }
309
310 // back-end
311 static void
312 DrawHistograms ()
313 {
314     VisualizationData vd;
315
316     if( InitVisualization( &vd ) ) {
317         if( vd.hist_width < MIN_HIST_WIDTH ) {
318             DrawHistogramAsDiagram( vd.cy, vd.paint_width, vd.hist_count );
319         }
320         else {
321             DrawHistogramFull( vd.cy, vd.hist_width, vd.hist_count );
322         }
323     }
324 }
325
326 // back-end
327 int
328 GetMoveIndexFromPoint (int x, int y)
329 {
330     int result = -1;
331     int start_x = MarginX + MarginW;
332     VisualizationData vd;
333
334     if( x >= start_x && InitVisualization( &vd ) ) {
335         /* Almost an hack here... we duplicate some of the paint logic */
336         if( vd.hist_width < MIN_HIST_WIDTH ) {
337             double step;
338
339             vd.hist_count -= vd.hist_count % 8;
340             vd.hist_count += 8;
341             vd.hist_count /= 2;
342
343             step = (double) vd.paint_width / (vd.hist_count + 1);
344             step /= 2;
345
346             result = (int) (0.5 + (double) (x - start_x) / step);
347         }
348         else {
349             result = (x - start_x) / vd.hist_width;
350         }
351     }
352
353     if( result >= currLast ) {
354         result = -1;
355     }
356
357     return result;
358 }
359
360 // init and display part split of so they can be moved to front end
361 void
362 PaintEvalGraph (void)
363 {
364     VariantClass v = gameInfo.variant;
365     range = (gameInfo.holdingsWidth && v != VariantSuper && v != VariantGreat && v != VariantSChess) ? 2 : 1; // [HGM] double range in drop games
366     /* Draw */
367     DrawRectangle(0, 0, nWidthPB, nHeightPB, 2, FILLED);
368     DrawAxis();
369     DrawHistograms();
370 }