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