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