Updated all files to GPL version 3.
[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[8];\r
149         int cbBuf;\r
150 \r
151         if( value > 0 ) {\r
152             buf[0] = '+';\r
153             itoa( value, buf+1, 10 );\r
154         }\r
155         else {\r
156             itoa( value, buf, 10 );\r
157         }\r
158 \r
159         cbBuf = strlen( buf );\r
160 \r
161         GetTextExtentPoint32( hdcPB, buf, cbBuf, &stSize );\r
162 \r
163         TextOut( hdcPB, MarginX - stSize.cx - 2, y - stSize.cy / 2, buf, cbBuf );\r
164     }\r
165 }\r
166 \r
167 static VOID DrawAxis()\r
168 {\r
169     int cy = nHeightPB / 2;\r
170     \r
171     SelectObject( hdcPB, GetStockObject(NULL_BRUSH) );\r
172 \r
173     SetBkMode( hdcPB, TRANSPARENT );\r
174 \r
175     DrawAxisSegmentHoriz( +5, TRUE );\r
176     DrawAxisSegmentHoriz( +3, FALSE );\r
177     DrawAxisSegmentHoriz( +1, FALSE );\r
178     DrawAxisSegmentHoriz(  0, TRUE );\r
179     DrawAxisSegmentHoriz( -1, FALSE );\r
180     DrawAxisSegmentHoriz( -3, FALSE );\r
181     DrawAxisSegmentHoriz( -5, TRUE );\r
182 \r
183     SelectObject( hdcPB, GetStockObject(BLACK_PEN) );\r
184 \r
185     DrawLine( MarginX + MarginW, cy, nWidthPB - MarginW, cy );\r
186     DrawLine( MarginX + MarginW, MarginH, MarginX + MarginW, nHeightPB - MarginH );\r
187 }\r
188 \r
189 static VOID DrawHistogram( int x, int y, int width, int value, int side )\r
190 {\r
191     RECT rc;\r
192 \r
193     if( value > -25 && value < +25 ) return;\r
194 \r
195     rc.left = x;\r
196     rc.right = rc.left + width + 1;\r
197 \r
198     if( value > 0 ) {\r
199         rc.top = GetValueY( value );\r
200         rc.bottom = y+1;\r
201     }\r
202     else {\r
203         rc.top = y;\r
204         rc.bottom = GetValueY( value ) + 1;\r
205     }\r
206 \r
207 \r
208     if( width == MIN_HIST_WIDTH ) {\r
209         rc.right--;\r
210         FillRect( hdcPB, &rc, hbrHist[side] );\r
211     }\r
212     else {\r
213         SelectObject( hdcPB, hbrHist[side] );\r
214         Rectangle( hdcPB, rc.left, rc.top, rc.right, rc.bottom );\r
215     }\r
216 }\r
217 \r
218 static VOID DrawSeparator( int index, int x )\r
219 {\r
220     if( index > 0 ) {\r
221         if( index == currCurrent ) {\r
222             HPEN hp = SelectObject( hdcPB, hpenBlueDotted );\r
223             DrawLineEx( x, MarginH, x, nHeightPB - MarginH );\r
224             SelectObject( hdcPB, hp );\r
225         }\r
226         else if( (index % 20) == 0 ) {\r
227             HPEN hp = SelectObject( hdcPB, hpenDotted );\r
228             DrawLineEx( x, MarginH, x, nHeightPB - MarginH );\r
229             SelectObject( hdcPB, hp );\r
230         }\r
231     }\r
232 }\r
233 \r
234 /* Actually draw histogram as a diagram, cause there's too much data */\r
235 static VOID DrawHistogramAsDiagram( int cy, int paint_width, int hist_count )\r
236 {\r
237     double step;\r
238     int i;\r
239 \r
240     /* Rescale the graph every few moves (as opposed to every move) */\r
241     hist_count -= hist_count % 8;\r
242     hist_count += 8;\r
243     hist_count /= 2;\r
244 \r
245     step = (double) paint_width / (hist_count + 1);\r
246 \r
247     for( i=0; i<2; i++ ) {\r
248         int index = currFirst;\r
249         int side = (currCurrent + i + 1) & 1; /* Draw current side last */\r
250         double x = MarginX + MarginW;\r
251 \r
252         if( (index & 1) != side ) {\r
253             x += step / 2;\r
254             index++;\r
255         }\r
256 \r
257         SelectObject( hdcPB, hpenBold[side] );\r
258 \r
259         MoveToEx( hdcPB, (int) x, cy, NULL );\r
260 \r
261         index += 2;\r
262 \r
263         while( index < currLast ) {\r
264             x += step;\r
265 \r
266             DrawSeparator( index, (int) x );\r
267 \r
268             /* Extend line up to current point */\r
269             if( currPvInfo[index].depth > 0 ) {\r
270                 LineTo( hdcPB, (int) x, GetValueY( GetPvScore(index) ) );\r
271             }\r
272 \r
273             index += 2;\r
274         }\r
275     }\r
276 }\r
277 \r
278 static VOID DrawHistogramFull( int cy, int hist_width, int hist_count )\r
279 {\r
280     int i;\r
281 \r
282     SelectObject( hdcPB, GetStockObject(BLACK_PEN) );\r
283 \r
284     for( i=0; i<hist_count; i++ ) {\r
285         int index = currFirst + i;\r
286         int x = MarginX + MarginW + index * hist_width;\r
287 \r
288         /* Draw a separator every 10 moves */\r
289         DrawSeparator( index, x );\r
290 \r
291         /* Draw histogram */\r
292         if( currPvInfo[i].depth > 0 ) {\r
293             DrawHistogram( x, cy, hist_width, GetPvScore(index), index & 1 );\r
294         }\r
295     }\r
296 }\r
297 \r
298 typedef struct {\r
299     int cy;\r
300     int hist_width;\r
301     int hist_count;\r
302     int paint_width;\r
303 } VisualizationData;\r
304 \r
305 static BOOL InitVisualization( VisualizationData * vd )\r
306 {\r
307     BOOL result = FALSE;\r
308 \r
309     vd->cy = nHeightPB / 2;\r
310     vd->hist_width = MIN_HIST_WIDTH;\r
311     vd->hist_count = currLast - currFirst;\r
312     vd->paint_width = nWidthPB - MarginX - 2*MarginW;\r
313 \r
314     if( vd->hist_count > 0 ) {\r
315         result = TRUE;\r
316 \r
317         /* Compute width */\r
318         vd->hist_width = vd->paint_width / vd->hist_count;\r
319 \r
320         if( vd->hist_width > MAX_HIST_WIDTH ) vd->hist_width = MAX_HIST_WIDTH;\r
321 \r
322         vd->hist_width -= vd->hist_width % 2;\r
323     }\r
324 \r
325     return result;\r
326 }\r
327 \r
328 static VOID DrawHistograms()\r
329 {\r
330     VisualizationData vd;\r
331 \r
332     if( InitVisualization( &vd ) ) {\r
333         if( vd.hist_width < MIN_HIST_WIDTH ) {\r
334             DrawHistogramAsDiagram( vd.cy, vd.paint_width, vd.hist_count );\r
335         }\r
336         else {\r
337             DrawHistogramFull( vd.cy, vd.hist_width, vd.hist_count );\r
338         }\r
339     }\r
340 }\r
341 \r
342 static int GetMoveIndexFromPoint( int x, int y )\r
343 {\r
344     int result = -1;\r
345     int start_x = MarginX + MarginW;\r
346     VisualizationData vd;\r
347 \r
348     if( x >= start_x && InitVisualization( &vd ) ) {\r
349         /* Almost an hack here... we duplicate some of the paint logic */\r
350         if( vd.hist_width < MIN_HIST_WIDTH ) {\r
351             double step;\r
352 \r
353             vd.hist_count -= vd.hist_count % 8;\r
354             vd.hist_count += 8;\r
355             vd.hist_count /= 2;\r
356 \r
357             step = (double) vd.paint_width / (vd.hist_count + 1);\r
358             step /= 2;\r
359 \r
360             result = (int) (0.5 + (double) (x - start_x) / step);\r
361         }\r
362         else {\r
363             result = (x - start_x) / vd.hist_width;\r
364         }\r
365     }\r
366 \r
367     if( result >= currLast ) {\r
368         result = -1;\r
369     }\r
370 \r
371     return result;\r
372 }\r
373 \r
374 static VOID DrawBackground()\r
375 {\r
376     HBRUSH hbr;\r
377     RECT rc;\r
378 \r
379     hbr = CreateBrush( BS_SOLID, GetSysColor( COLOR_3DFACE ) );\r
380 \r
381     rc.left = 0;\r
382     rc.top = 0;\r
383     rc.right = nWidthPB;\r
384     rc.bottom = nHeightPB;\r
385 \r
386     FillRect( hdcPB, &rc, hbr );\r
387 \r
388     DeleteObject( hbr );\r
389 }\r
390 \r
391 static VOID PaintEvalGraph( HWND hWnd, HDC hDC )\r
392 {\r
393     RECT rcClient;\r
394     int width;\r
395     int height;\r
396 \r
397     /* Get client area */\r
398     GetClientRect( hWnd, &rcClient );\r
399 \r
400     width = rcClient.right - rcClient.left;\r
401     height = rcClient.bottom - rcClient.top;\r
402 \r
403     /* Create or recreate paint box if needed */\r
404     if( hbmPB == NULL || width != nWidthPB || height != nHeightPB ) {\r
405         if( hpenDotted == NULL ) {\r
406             hpenDotted = CreatePen( PS_DOT, 0, RGB(0xA0,0xA0,0xA0) );\r
407             hpenBlueDotted = CreatePen( PS_DOT, 0, RGB(0x00,0x00,0xFF) );\r
408             hpenBold[0] = CreatePen( PS_SOLID, 2, crWhite );\r
409             hpenBold[1] = CreatePen( PS_SOLID, 2, crBlack );\r
410             hbrHist[0] = CreateBrush( BS_SOLID, crWhite );\r
411             hbrHist[1] = CreateBrush( BS_SOLID, crBlack );\r
412         }\r
413 \r
414         if( hdcPB != NULL ) {\r
415             DeleteDC( hdcPB );\r
416             hdcPB = NULL;\r
417         }\r
418 \r
419         if( hbmPB != NULL ) {\r
420             DeleteObject( hbmPB );\r
421             hbmPB = NULL;\r
422         }\r
423 \r
424         hdcPB = CreateCompatibleDC( hDC );\r
425 \r
426         nWidthPB = width;\r
427         nHeightPB = height;\r
428         hbmPB = CreateCompatibleBitmap( hDC, nWidthPB, nHeightPB );\r
429 \r
430         SelectObject( hdcPB, hbmPB );\r
431     }\r
432 \r
433     /* Draw */\r
434     DrawBackground();\r
435     DrawAxis();\r
436     DrawHistograms();\r
437 \r
438     /* Copy bitmap into destination DC */\r
439     BitBlt( hDC, 0, 0, width, height, hdcPB, 0, 0, SRCCOPY );\r
440 }\r
441 \r
442 LRESULT CALLBACK EvalGraphProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )\r
443 {\r
444     static SnapData sd;\r
445 \r
446     PAINTSTRUCT stPS;\r
447     HDC hDC;\r
448 \r
449     switch (message) {\r
450     case WM_INITDIALOG:\r
451         if( evalGraphDialog == NULL ) {\r
452             evalGraphDialog = hDlg;\r
453 \r
454             RestoreWindowPlacement( hDlg, &wpEvalGraph ); /* Restore window placement */\r
455         }\r
456 \r
457         return FALSE;\r
458 \r
459     case WM_COMMAND:\r
460         switch (LOWORD(wParam)) {\r
461         case IDOK:\r
462           EndDialog(hDlg, TRUE);\r
463           return TRUE;\r
464 \r
465         case IDCANCEL:\r
466           EndDialog(hDlg, FALSE);\r
467           return TRUE;\r
468 \r
469         default:\r
470           break;\r
471         }\r
472 \r
473         break;\r
474 \r
475     case WM_ERASEBKGND:\r
476         return TRUE;\r
477 \r
478     case WM_PAINT:\r
479         hDC = BeginPaint( hDlg, &stPS );\r
480         PaintEvalGraph( hDlg, hDC );\r
481         EndPaint( hDlg, &stPS );\r
482         break;\r
483 \r
484     case WM_REFRESH_GRAPH:\r
485         hDC = GetDC( hDlg );\r
486         PaintEvalGraph( hDlg, hDC );\r
487         ReleaseDC( hDlg, hDC );\r
488         break;\r
489 \r
490     case WM_LBUTTONDBLCLK:\r
491         if( wParam == 0 || wParam == MK_LBUTTON ) {\r
492             int index = GetMoveIndexFromPoint( LOWORD(lParam), HIWORD(lParam) );\r
493 \r
494             if( index >= 0 && index < currLast ) {\r
495                 ToNrEvent( index + 1 );\r
496             }\r
497         }\r
498         return TRUE;\r
499 \r
500     case WM_SIZE:\r
501         InvalidateRect( hDlg, NULL, FALSE );\r
502         break;\r
503 \r
504     case WM_GETMINMAXINFO:\r
505         {\r
506             MINMAXINFO * mmi = (MINMAXINFO *) lParam;\r
507         \r
508             mmi->ptMinTrackSize.x = 100;\r
509             mmi->ptMinTrackSize.y = 100;\r
510         }\r
511         break;\r
512 \r
513     /* Support for captionless window */\r
514 #if 0\r
515     case WM_NCLBUTTONDBLCLK:\r
516         if( wParam == HTCAPTION ) {\r
517             int index;\r
518             POINT mouse_xy;\r
519             POINTS pts = MAKEPOINTS(lParam);\r
520 \r
521             mouse_xy.x = pts.x;\r
522             mouse_xy.y = pts.y;\r
523             ScreenToClient( hDlg, &mouse_xy );\r
524 \r
525             index = GetMoveIndexFromPoint( mouse_xy.x, mouse_xy.y );\r
526 \r
527             if( index >= 0 && index < currLast ) {\r
528                 ToNrEvent( index + 1 );\r
529             }\r
530         }\r
531         break;\r
532 \r
533     case WM_NCHITTEST:\r
534         {\r
535             LRESULT res = DefWindowProc( hDlg, message, wParam, lParam );\r
536 \r
537             if( res == HTCLIENT ) res = HTCAPTION;\r
538 \r
539             SetWindowLong( hDlg, DWL_MSGRESULT, res );\r
540 \r
541             return TRUE;\r
542         }\r
543         break;\r
544 #endif\r
545 \r
546     case WM_CLOSE:\r
547         EvalGraphPopDown();\r
548         break;\r
549 \r
550     case WM_ENTERSIZEMOVE:\r
551         return OnEnterSizeMove( &sd, hDlg, wParam, lParam );\r
552 \r
553     case WM_SIZING:\r
554         return OnSizing( &sd, hDlg, wParam, lParam );\r
555 \r
556     case WM_MOVING:\r
557         return OnMoving( &sd, hDlg, wParam, lParam );\r
558 \r
559     case WM_EXITSIZEMOVE:\r
560         return OnExitSizeMove( &sd, hDlg, wParam, lParam );\r
561     }\r
562 \r
563     return FALSE;\r
564 }\r
565 \r
566 VOID EvalGraphPopUp()\r
567 {\r
568   FARPROC lpProc;\r
569   \r
570   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEvalGraph, MF_CHECKED);\r
571 \r
572   if( evalGraphDialog ) {\r
573     SendMessage( evalGraphDialog, WM_INITDIALOG, 0, 0 );\r
574 \r
575     if( ! evalGraphDialogUp ) {\r
576         ShowWindow(evalGraphDialog, SW_SHOW);\r
577     }\r
578   }\r
579   else {\r
580     crWhite = appData.evalHistColorWhite;\r
581     crBlack = appData.evalHistColorBlack;\r
582 \r
583     lpProc = MakeProcInstance( (FARPROC) EvalGraphProc, hInst );\r
584 \r
585     /* Note to self: dialog must have the WS_VISIBLE style set, otherwise it's not shown! */\r
586     CreateDialog( hInst, MAKEINTRESOURCE(DLG_EvalGraph), hwndMain, (DLGPROC)lpProc );\r
587 \r
588     FreeProcInstance(lpProc);\r
589   }\r
590 \r
591   evalGraphDialogUp = TRUE;\r
592 }\r
593 \r
594 VOID EvalGraphPopDown()\r
595 {\r
596   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEvalGraph, MF_UNCHECKED);\r
597 \r
598   if( evalGraphDialog ) {\r
599       ShowWindow(evalGraphDialog, SW_HIDE);\r
600   }\r
601 \r
602   evalGraphDialogUp = FALSE;\r
603 }\r
604 \r
605 VOID EvalGraphSet( int first, int last, int current, ChessProgramStats_Move * pvInfo )\r
606 {\r
607     /* [AS] Danger! For now we rely on the pvInfo parameter being a static variable! */\r
608 \r
609     currFirst = first;\r
610     currLast = last;\r
611     currCurrent = current;\r
612     currPvInfo = pvInfo;\r
613 \r
614     if( evalGraphDialog ) {\r
615         SendMessage( evalGraphDialog, WM_REFRESH_GRAPH, 0, 0 );\r
616     }\r
617 }\r
618 \r
619 BOOL EvalGraphIsUp()\r
620 {\r
621     return evalGraphDialogUp;\r
622 }\r