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