3e57bb998f39313e11f152ac163b17d2cec1758e
[xboard.git] / winboard / wevalgraph.c
1 /*\r
2  * Evaluation graph\r
3  *\r
4  * Author: Alessandro Scotti (Dec 2005)\r
5  *\r
6  * ------------------------------------------------------------------------\r
7  *\r
8  * GNU XBoard is free software: you can redistribute it and/or modify\r
9  * it under the terms of the GNU General Public License as published by\r
10  * the Free Software Foundation, either version 3 of the License, or (at\r
11  * your option) any later version.\r
12  *\r
13  * GNU XBoard is distributed in the hope that it will be useful, but\r
14  * WITHOUT ANY WARRANTY; without even the implied warranty of\r
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
16  * General Public License for more details.\r
17  *\r
18  * You should have received a copy of the GNU General Public License\r
19  * along with this program. If not, see http://www.gnu.org/licenses/.  *\r
20  *\r
21  *------------------------------------------------------------------------\r
22  ** See the file ChangeLog for a revision history.  */\r
23 \r
24 #include "config.h"\r
25 \r
26 #include <windows.h> /* required for all Windows applications */\r
27 #include <richedit.h>\r
28 #include <stdio.h>\r
29 #include <stdlib.h>\r
30 #include <malloc.h>\r
31 #include <commdlg.h>\r
32 #include <dlgs.h>\r
33 \r
34 #include "common.h"\r
35 #include "winboard.h"\r
36 #include "frontend.h"\r
37 #include "backend.h"\r
38 \r
39 #include "wsnap.h"\r
40 \r
41 VOID EvalGraphSet( int first, int last, int current, ChessProgramStats_Move * pvInfo );\r
42 VOID EvalGraphPopUp();\r
43 VOID EvalGraphPopDown();\r
44 BOOL EvalGraphIsUp();\r
45 \r
46 #define WM_REFRESH_GRAPH    (WM_USER + 1)\r
47 \r
48 /* Imports from backend.c */\r
49 char * SavePart(char *str);\r
50 \r
51 /* Imports from winboard.c */\r
52 extern HWND evalGraphDialog;\r
53 extern BOOLEAN evalGraphDialogUp;\r
54 \r
55 extern HINSTANCE hInst;\r
56 extern HWND hwndMain;\r
57 \r
58 extern WindowPlacement wpEvalGraph;\r
59 \r
60 /* Module globals */\r
61 static ChessProgramStats_Move * currPvInfo;\r
62 static int currFirst = 0;\r
63 static int currLast = 0;\r
64 static int currCurrent = -1;\r
65 \r
66 static COLORREF crWhite = RGB( 0xFF, 0xFF, 0xB0 );\r
67 static COLORREF crBlack = RGB( 0xAD, 0x5D, 0x3D );\r
68 \r
69 static HDC hdcPB = NULL;\r
70 static HBITMAP hbmPB = NULL;\r
71 static int nWidthPB = 0;\r
72 static int nHeightPB = 0;\r
73 static HPEN hpenDotted = NULL;\r
74 static HPEN hpenBlueDotted = NULL;\r
75 static HPEN hpenBold[2] = { NULL, NULL };\r
76 static HBRUSH hbrHist[2] = { NULL, NULL };\r
77 \r
78 static int MarginX = 18;\r
79 static int MarginW = 4;\r
80 static int MarginH = 4;\r
81 \r
82 #define MIN_HIST_WIDTH  4\r
83 #define MAX_HIST_WIDTH  10\r
84 \r
85 static int GetPvScore( int index )\r
86 {\r
87     int score = currPvInfo[ index ].score;\r
88 \r
89     if( index & 1 ) score = -score; /* Flip score for black */\r
90 \r
91     return score;\r
92 }\r
93 \r
94 static VOID DrawLine( int x1, int y1, int x2, int y2 )\r
95 {\r
96     MoveToEx( hdcPB, x1, y1, NULL );\r
97 \r
98     LineTo( hdcPB, x2, y2 );\r
99 }\r
100 \r
101 static VOID DrawLineEx( int x1, int y1, int x2, int y2 )\r
102 {\r
103     POINT stPT;\r
104 \r
105     MoveToEx( hdcPB, x1, y1, &stPT );\r
106 \r
107     LineTo( hdcPB, x2, y2 );\r
108 \r
109     MoveToEx( hdcPB, stPT.x, stPT.y, NULL );\r
110 }\r
111 \r
112 static HBRUSH CreateBrush( UINT style, COLORREF color )\r
113 {\r
114     LOGBRUSH stLB;\r
115 \r
116     stLB.lbStyle = style;\r
117     stLB.lbColor = color;\r
118     stLB.lbHatch = 0;\r
119 \r
120     return CreateBrushIndirect( &stLB );\r
121 }\r
122 \r
123 /*\r
124     For a centipawn value, this function returns the height of the corresponding\r
125     histogram, centered on the reference axis.\r
126 \r
127     Note: height can be negative!\r
128 */\r
129 static int GetValueY( int value )\r
130 {\r
131     if( value < -700 ) value = -700;\r
132     if( value > +700 ) value = +700;\r
133 \r
134     return (nHeightPB / 2) - (int)(value * (nHeightPB - 2*MarginH) / 1400.0);\r
135 }\r
136 \r
137 static VOID DrawAxisSegmentHoriz( int value, BOOL drawValue )\r
138 {\r
139     int y = GetValueY( value*100 );\r
140 \r
141     SelectObject( hdcPB, GetStockObject(BLACK_PEN) );\r
142     DrawLine( MarginX, y, MarginX + MarginW, y );\r
143     SelectObject( hdcPB, hpenDotted );\r
144     DrawLine( MarginX + MarginW, y, nWidthPB - MarginW, y );\r
145 \r
146     if( drawValue ) {\r
147         SIZE stSize;\r
148         char buf[MSG_SIZ], *b = buf;\r
149         int cbBuf;\r
150 \r
151         if( value > 0 ) *b++ = '+';\r
152         sprintf(b, "%d", value);\r
153 \r
154         cbBuf = strlen( buf );\r
155         GetTextExtentPoint32( hdcPB, buf, cbBuf, &stSize );\r
156         TextOut( hdcPB, MarginX - stSize.cx - 2, y - stSize.cy / 2, buf, cbBuf );\r
157     }\r
158 }\r
159 \r
160 static VOID DrawAxis()\r
161 {\r
162     int cy = nHeightPB / 2;\r
163     \r
164     SelectObject( hdcPB, GetStockObject(NULL_BRUSH) );\r
165 \r
166     SetBkMode( hdcPB, TRANSPARENT );\r
167 \r
168     DrawAxisSegmentHoriz( +5, TRUE );\r
169     DrawAxisSegmentHoriz( +3, FALSE );\r
170     DrawAxisSegmentHoriz( +1, FALSE );\r
171     DrawAxisSegmentHoriz(  0, TRUE );\r
172     DrawAxisSegmentHoriz( -1, FALSE );\r
173     DrawAxisSegmentHoriz( -3, FALSE );\r
174     DrawAxisSegmentHoriz( -5, TRUE );\r
175 \r
176     SelectObject( hdcPB, GetStockObject(BLACK_PEN) );\r
177 \r
178     DrawLine( MarginX + MarginW, cy, nWidthPB - MarginW, cy );\r
179     DrawLine( MarginX + MarginW, MarginH, MarginX + MarginW, nHeightPB - MarginH );\r
180 }\r
181 \r
182 static VOID DrawHistogram( int x, int y, int width, int value, int side )\r
183 {\r
184     RECT rc;\r
185 \r
186     if( value > -25 && value < +25 ) return;\r
187 \r
188     rc.left = x;\r
189     rc.right = rc.left + width + 1;\r
190 \r
191     if( value > 0 ) {\r
192         rc.top = GetValueY( value );\r
193         rc.bottom = y+1;\r
194     }\r
195     else {\r
196         rc.top = y;\r
197         rc.bottom = GetValueY( value ) + 1;\r
198     }\r
199 \r
200 \r
201     if( width == MIN_HIST_WIDTH ) {\r
202         rc.right--;\r
203         FillRect( hdcPB, &rc, hbrHist[side] );\r
204     }\r
205     else {\r
206         SelectObject( hdcPB, hbrHist[side] );\r
207         Rectangle( hdcPB, rc.left, rc.top, rc.right, rc.bottom );\r
208     }\r
209 }\r
210 \r
211 static VOID DrawSeparator( int index, int x )\r
212 {\r
213     if( index > 0 ) {\r
214         if( index == currCurrent ) {\r
215             HPEN hp = SelectObject( hdcPB, hpenBlueDotted );\r
216             DrawLineEx( x, MarginH, x, nHeightPB - MarginH );\r
217             SelectObject( hdcPB, hp );\r
218         }\r
219         else if( (index % 20) == 0 ) {\r
220             HPEN hp = SelectObject( hdcPB, hpenDotted );\r
221             DrawLineEx( x, MarginH, x, nHeightPB - MarginH );\r
222             SelectObject( hdcPB, hp );\r
223         }\r
224     }\r
225 }\r
226 \r
227 /* Actually draw histogram as a diagram, cause there's too much data */\r
228 static VOID DrawHistogramAsDiagram( int cy, int paint_width, int hist_count )\r
229 {\r
230     double step;\r
231     int i;\r
232 \r
233     /* Rescale the graph every few moves (as opposed to every move) */\r
234     hist_count -= hist_count % 8;\r
235     hist_count += 8;\r
236     hist_count /= 2;\r
237 \r
238     step = (double) paint_width / (hist_count + 1);\r
239 \r
240     for( i=0; i<2; i++ ) {\r
241         int index = currFirst;\r
242         int side = (currCurrent + i + 1) & 1; /* Draw current side last */\r
243         double x = MarginX + MarginW;\r
244 \r
245         if( (index & 1) != side ) {\r
246             x += step / 2;\r
247             index++;\r
248         }\r
249 \r
250         SelectObject( hdcPB, hpenBold[side] );\r
251 \r
252         MoveToEx( hdcPB, (int) x, cy, NULL );\r
253 \r
254         index += 2;\r
255 \r
256         while( index < currLast ) {\r
257             x += step;\r
258 \r
259             DrawSeparator( index, (int) x );\r
260 \r
261             /* Extend line up to current point */\r
262             if( currPvInfo[index].depth > 0 ) {\r
263                 LineTo( hdcPB, (int) x, GetValueY( GetPvScore(index) ) );\r
264             }\r
265 \r
266             index += 2;\r
267         }\r
268     }\r
269 }\r
270 \r
271 static VOID DrawHistogramFull( int cy, int hist_width, int hist_count )\r
272 {\r
273     int i;\r
274 \r
275     SelectObject( hdcPB, GetStockObject(BLACK_PEN) );\r
276 \r
277     for( i=0; i<hist_count; i++ ) {\r
278         int index = currFirst + i;\r
279         int x = MarginX + MarginW + index * hist_width;\r
280 \r
281         /* Draw a separator every 10 moves */\r
282         DrawSeparator( index, x );\r
283 \r
284         /* Draw histogram */\r
285         if( currPvInfo[i].depth > 0 ) {\r
286             DrawHistogram( x, cy, hist_width, GetPvScore(index), index & 1 );\r
287         }\r
288     }\r
289 }\r
290 \r
291 typedef struct {\r
292     int cy;\r
293     int hist_width;\r
294     int hist_count;\r
295     int paint_width;\r
296 } VisualizationData;\r
297 \r
298 static BOOL InitVisualization( VisualizationData * vd )\r
299 {\r
300     BOOL result = FALSE;\r
301 \r
302     vd->cy = nHeightPB / 2;\r
303     vd->hist_width = MIN_HIST_WIDTH;\r
304     vd->hist_count = currLast - currFirst;\r
305     vd->paint_width = nWidthPB - MarginX - 2*MarginW;\r
306 \r
307     if( vd->hist_count > 0 ) {\r
308         result = TRUE;\r
309 \r
310         /* Compute width */\r
311         vd->hist_width = vd->paint_width / vd->hist_count;\r
312 \r
313         if( vd->hist_width > MAX_HIST_WIDTH ) vd->hist_width = MAX_HIST_WIDTH;\r
314 \r
315         vd->hist_width -= vd->hist_width % 2;\r
316     }\r
317 \r
318     return result;\r
319 }\r
320 \r
321 static VOID DrawHistograms()\r
322 {\r
323     VisualizationData vd;\r
324 \r
325     if( InitVisualization( &vd ) ) {\r
326         if( vd.hist_width < MIN_HIST_WIDTH ) {\r
327             DrawHistogramAsDiagram( vd.cy, vd.paint_width, vd.hist_count );\r
328         }\r
329         else {\r
330             DrawHistogramFull( vd.cy, vd.hist_width, vd.hist_count );\r
331         }\r
332     }\r
333 }\r
334 \r
335 static int GetMoveIndexFromPoint( int x, int y )\r
336 {\r
337     int result = -1;\r
338     int start_x = MarginX + MarginW;\r
339     VisualizationData vd;\r
340 \r
341     if( x >= start_x && InitVisualization( &vd ) ) {\r
342         /* Almost an hack here... we duplicate some of the paint logic */\r
343         if( vd.hist_width < MIN_HIST_WIDTH ) {\r
344             double step;\r
345 \r
346             vd.hist_count -= vd.hist_count % 8;\r
347             vd.hist_count += 8;\r
348             vd.hist_count /= 2;\r
349 \r
350             step = (double) vd.paint_width / (vd.hist_count + 1);\r
351             step /= 2;\r
352 \r
353             result = (int) (0.5 + (double) (x - start_x) / step);\r
354         }\r
355         else {\r
356             result = (x - start_x) / vd.hist_width;\r
357         }\r
358     }\r
359 \r
360     if( result >= currLast ) {\r
361         result = -1;\r
362     }\r
363 \r
364     return result;\r
365 }\r
366 \r
367 static VOID DrawBackground()\r
368 {\r
369     HBRUSH hbr;\r
370     RECT rc;\r
371 \r
372     hbr = CreateBrush( BS_SOLID, GetSysColor( COLOR_3DFACE ) );\r
373 \r
374     rc.left = 0;\r
375     rc.top = 0;\r
376     rc.right = nWidthPB;\r
377     rc.bottom = nHeightPB;\r
378 \r
379     FillRect( hdcPB, &rc, hbr );\r
380 \r
381     DeleteObject( hbr );\r
382 }\r
383 \r
384 static VOID PaintEvalGraph( HWND hWnd, HDC hDC )\r
385 {\r
386     RECT rcClient;\r
387     int width;\r
388     int height;\r
389 \r
390     /* Get client area */\r
391     GetClientRect( hWnd, &rcClient );\r
392 \r
393     width = rcClient.right - rcClient.left;\r
394     height = rcClient.bottom - rcClient.top;\r
395 \r
396     /* Create or recreate paint box if needed */\r
397     if( hbmPB == NULL || width != nWidthPB || height != nHeightPB ) {\r
398         if( hpenDotted == NULL ) {\r
399             hpenDotted = CreatePen( PS_DOT, 0, RGB(0xA0,0xA0,0xA0) );\r
400             hpenBlueDotted = CreatePen( PS_DOT, 0, RGB(0x00,0x00,0xFF) );\r
401             hpenBold[0] = CreatePen( PS_SOLID, 2, crWhite );\r
402             hpenBold[1] = CreatePen( PS_SOLID, 2, crBlack );\r
403             hbrHist[0] = CreateBrush( BS_SOLID, crWhite );\r
404             hbrHist[1] = CreateBrush( BS_SOLID, crBlack );\r
405         }\r
406 \r
407         if( hdcPB != NULL ) {\r
408             DeleteDC( hdcPB );\r
409             hdcPB = NULL;\r
410         }\r
411 \r
412         if( hbmPB != NULL ) {\r
413             DeleteObject( hbmPB );\r
414             hbmPB = NULL;\r
415         }\r
416 \r
417         hdcPB = CreateCompatibleDC( hDC );\r
418 \r
419         nWidthPB = width;\r
420         nHeightPB = height;\r
421         hbmPB = CreateCompatibleBitmap( hDC, nWidthPB, nHeightPB );\r
422 \r
423         SelectObject( hdcPB, hbmPB );\r
424     }\r
425 \r
426     /* Draw */\r
427     DrawBackground();\r
428     DrawAxis();\r
429     DrawHistograms();\r
430 \r
431     /* Copy bitmap into destination DC */\r
432     BitBlt( hDC, 0, 0, width, height, hdcPB, 0, 0, SRCCOPY );\r
433 }\r
434 \r
435 LRESULT CALLBACK EvalGraphProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )\r
436 {\r
437     static SnapData sd;\r
438 \r
439     PAINTSTRUCT stPS;\r
440     HDC hDC;\r
441 \r
442     switch (message) {\r
443     case WM_INITDIALOG:\r
444         if( evalGraphDialog == NULL ) {\r
445             evalGraphDialog = hDlg;\r
446 \r
447             RestoreWindowPlacement( hDlg, &wpEvalGraph ); /* Restore window placement */\r
448         }\r
449 \r
450         return FALSE;\r
451 \r
452     case WM_COMMAND:\r
453         switch (LOWORD(wParam)) {\r
454         case IDOK:\r
455           EndDialog(hDlg, TRUE);\r
456           return TRUE;\r
457 \r
458         case IDCANCEL:\r
459           EndDialog(hDlg, FALSE);\r
460           return TRUE;\r
461 \r
462         default:\r
463           break;\r
464         }\r
465 \r
466         break;\r
467 \r
468     case WM_ERASEBKGND:\r
469         return TRUE;\r
470 \r
471     case WM_PAINT:\r
472         hDC = BeginPaint( hDlg, &stPS );\r
473         PaintEvalGraph( hDlg, hDC );\r
474         EndPaint( hDlg, &stPS );\r
475         break;\r
476 \r
477     case WM_REFRESH_GRAPH:\r
478         hDC = GetDC( hDlg );\r
479         PaintEvalGraph( hDlg, hDC );\r
480         ReleaseDC( hDlg, hDC );\r
481         break;\r
482 \r
483     case WM_LBUTTONDBLCLK:\r
484         if( wParam == 0 || wParam == MK_LBUTTON ) {\r
485             int index = GetMoveIndexFromPoint( LOWORD(lParam), HIWORD(lParam) );\r
486 \r
487             if( index >= 0 && index < currLast ) {\r
488                 ToNrEvent( index + 1 );\r
489             }\r
490         }\r
491         return TRUE;\r
492 \r
493     case WM_SIZE:\r
494         InvalidateRect( hDlg, NULL, FALSE );\r
495         break;\r
496 \r
497     case WM_GETMINMAXINFO:\r
498         {\r
499             MINMAXINFO * mmi = (MINMAXINFO *) lParam;\r
500         \r
501             mmi->ptMinTrackSize.x = 100;\r
502             mmi->ptMinTrackSize.y = 100;\r
503         }\r
504         break;\r
505 \r
506     /* Support for captionless window */\r
507 #if 0\r
508     case WM_NCLBUTTONDBLCLK:\r
509         if( wParam == HTCAPTION ) {\r
510             int index;\r
511             POINT mouse_xy;\r
512             POINTS pts = MAKEPOINTS(lParam);\r
513 \r
514             mouse_xy.x = pts.x;\r
515             mouse_xy.y = pts.y;\r
516             ScreenToClient( hDlg, &mouse_xy );\r
517 \r
518             index = GetMoveIndexFromPoint( mouse_xy.x, mouse_xy.y );\r
519 \r
520             if( index >= 0 && index < currLast ) {\r
521                 ToNrEvent( index + 1 );\r
522             }\r
523         }\r
524         break;\r
525 \r
526     case WM_NCHITTEST:\r
527         {\r
528             LRESULT res = DefWindowProc( hDlg, message, wParam, lParam );\r
529 \r
530             if( res == HTCLIENT ) res = HTCAPTION;\r
531 \r
532             SetWindowLong( hDlg, DWL_MSGRESULT, res );\r
533 \r
534             return TRUE;\r
535         }\r
536         break;\r
537 #endif\r
538 \r
539     case WM_CLOSE:\r
540         EvalGraphPopDown();\r
541         break;\r
542 \r
543     case WM_ENTERSIZEMOVE:\r
544         return OnEnterSizeMove( &sd, hDlg, wParam, lParam );\r
545 \r
546     case WM_SIZING:\r
547         return OnSizing( &sd, hDlg, wParam, lParam );\r
548 \r
549     case WM_MOVING:\r
550         return OnMoving( &sd, hDlg, wParam, lParam );\r
551 \r
552     case WM_EXITSIZEMOVE:\r
553         return OnExitSizeMove( &sd, hDlg, wParam, lParam );\r
554     }\r
555 \r
556     return FALSE;\r
557 }\r
558 \r
559 VOID EvalGraphPopUp()\r
560 {\r
561   FARPROC lpProc;\r
562   \r
563   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEvalGraph, MF_CHECKED);\r
564 \r
565   if( evalGraphDialog ) {\r
566     SendMessage( evalGraphDialog, WM_INITDIALOG, 0, 0 );\r
567 \r
568     if( ! evalGraphDialogUp ) {\r
569         ShowWindow(evalGraphDialog, SW_SHOW);\r
570     }\r
571   }\r
572   else {\r
573     crWhite = appData.evalHistColorWhite;\r
574     crBlack = appData.evalHistColorBlack;\r
575 \r
576     lpProc = MakeProcInstance( (FARPROC) EvalGraphProc, hInst );\r
577 \r
578     /* Note to self: dialog must have the WS_VISIBLE style set, otherwise it's not shown! */\r
579     CreateDialog( hInst, MAKEINTRESOURCE(DLG_EvalGraph), hwndMain, (DLGPROC)lpProc );\r
580 \r
581     FreeProcInstance(lpProc);\r
582   }\r
583 \r
584   evalGraphDialogUp = TRUE;\r
585 }\r
586 \r
587 VOID EvalGraphPopDown()\r
588 {\r
589   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEvalGraph, MF_UNCHECKED);\r
590 \r
591   if( evalGraphDialog ) {\r
592       ShowWindow(evalGraphDialog, SW_HIDE);\r
593   }\r
594 \r
595   evalGraphDialogUp = FALSE;\r
596 }\r
597 \r
598 VOID EvalGraphSet( int first, int last, int current, ChessProgramStats_Move * pvInfo )\r
599 {\r
600     /* [AS] Danger! For now we rely on the pvInfo parameter being a static variable! */\r
601 \r
602     currFirst = first;\r
603     currLast = last;\r
604     currCurrent = current;\r
605     currPvInfo = pvInfo;\r
606 \r
607     if( evalGraphDialog ) {\r
608         SendMessage( evalGraphDialog, WM_REFRESH_GRAPH, 0, 0 );\r
609     }\r
610 }\r
611 \r
612 BOOL EvalGraphIsUp()\r
613 {\r
614     return evalGraphDialogUp;\r
615 }\r