4 * Author: Alessandro Scotti (Dec 2005)
\r
6 * Copyright 2005 Alessandro Scotti
\r
8 * ------------------------------------------------------------------------
\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
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
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
23 *------------------------------------------------------------------------
\r
24 ** See the file ChangeLog for a revision history. */
\r
26 // code refactored by HGM to obtain front-end / back-end separation
\r
30 #include <windows.h> /* required for all Windows applications */
\r
31 //include <richedit.h>
\r
33 //include <stdlib.h>
\r
34 //include <malloc.h>
\r
37 #include "frontend.h"
\r
38 #include "backend.h"
\r
40 /* Imports from winboard.c */
\r
41 extern BOOLEAN evalGraphDialogUp; // should be back-end variable, and defined here
\r
43 /* Module globals */ // used to communicate between back-end and front-end part
\r
44 static ChessProgramStats_Move * currPvInfo;
\r
45 static int currFirst = 0;
\r
46 static int currLast = 0;
\r
47 static int currCurrent = -1;
\r
49 static int nWidthPB = 0;
\r
50 static int nHeightPB = 0;
\r
52 static int MarginX = 18;
\r
53 static int MarginW = 4;
\r
54 static int MarginH = 4;
\r
56 #define MIN_HIST_WIDTH 4
\r
57 #define MAX_HIST_WIDTH 10
\r
61 #define PEN_DOTTED 2
\r
62 #define PEN_BLUEDOTTED 3
\r
63 #define PEN_BOLD 4 /* or 5 for black */
\r
68 // calls from back-end part into front-end
\r
69 static void DrawSegment( int x, int y, int *lastX, int *lastY, int penType );
\r
70 void DrawRectangle( int left, int top, int right, int bottom, int side, int style );
\r
71 void DrawEvalText(char *buf, int cbBuf, int y);
\r
75 static void DrawLine( int x1, int y1, int x2, int y2, int penType )
\r
77 DrawSegment( x1, y1, NULL, NULL, PEN_NONE );
\r
78 DrawSegment( x2, y2, NULL, NULL, penType );
\r
82 static void DrawLineEx( int x1, int y1, int x2, int y2, int penType )
\r
85 DrawSegment( x1, y1, &savX, &savY, PEN_NONE );
\r
86 DrawSegment( x2, y2, NULL, NULL, penType );
\r
87 DrawSegment( savX, savY, NULL, NULL, PEN_NONE );
\r
91 static int GetPvScore( int index )
\r
93 int score = currPvInfo[ index ].score;
\r
95 if( index & 1 ) score = -score; /* Flip score for black */
\r
102 For a centipawn value, this function returns the height of the corresponding
\r
103 histogram, centered on the reference axis.
\r
105 Note: height can be negative!
\r
107 static int GetValueY( int value )
\r
109 if( value < -700 ) value = -700;
\r
110 if( value > +700 ) value = +700;
\r
112 return (nHeightPB / 2) - (int)(value * (nHeightPB - 2*MarginH) / 1400.0);
\r
115 // the brush selection is made part of the DrawLine, by passing a style argument
\r
116 // the wrapper for doing the text output makes this back-end
\r
117 static void DrawAxisSegmentHoriz( int value, BOOL drawValue )
\r
119 int y = GetValueY( value*100 );
\r
122 char buf[MSG_SIZ], *b = buf;
\r
124 if( value > 0 ) *b++ = '+';
\r
125 sprintf(b, "%d", value);
\r
127 DrawEvalText(buf, strlen(buf), y);
\r
129 // [HGM] counts on DrawEvalText to have select transparent background for dotted line!
\r
130 DrawLine( MarginX, y, MarginX + MarginW, y, PEN_BLACK ); // Y-axis tick marks
\r
131 DrawLine( MarginX + MarginW, y, nWidthPB - MarginW, y, PEN_DOTTED ); // hor grid
\r
134 // The DrawLines again must select their own brush.
\r
135 // the initial brush selection is useless? BkMode needed for dotted line and text
\r
136 static void DrawAxis()
\r
138 int cy = nHeightPB / 2;
\r
140 // SelectObject( hdcPB, GetStockObject(NULL_BRUSH) );
\r
142 // SetBkMode( hdcPB, TRANSPARENT );
\r
144 DrawAxisSegmentHoriz( +5, TRUE );
\r
145 DrawAxisSegmentHoriz( +3, FALSE );
\r
146 DrawAxisSegmentHoriz( +1, FALSE );
\r
147 DrawAxisSegmentHoriz( 0, TRUE );
\r
148 DrawAxisSegmentHoriz( -1, FALSE );
\r
149 DrawAxisSegmentHoriz( -3, FALSE );
\r
150 DrawAxisSegmentHoriz( -5, TRUE );
\r
152 DrawLine( MarginX + MarginW, cy, nWidthPB - MarginW, cy, PEN_BLACK ); // x-axis
\r
153 DrawLine( MarginX + MarginW, MarginH, MarginX + MarginW, nHeightPB - MarginH, PEN_BLACK ); // y-axis
\r
157 static void DrawHistogram( int x, int y, int width, int value, int side )
\r
159 int left, top, right, bottom;
\r
161 if( value > -25 && value < +25 ) return;
\r
164 right = left + width + 1;
\r
167 top = GetValueY( value );
\r
172 bottom = GetValueY( value ) + 1;
\r
176 if( width == MIN_HIST_WIDTH ) {
\r
178 DrawRectangle( left, top, right, bottom, side, FILLED );
\r
181 DrawRectangle( left, top, right, bottom, side, OPEN );
\r
186 static void DrawSeparator( int index, int x )
\r
189 if( index == currCurrent ) {
\r
190 DrawLineEx( x, MarginH, x, nHeightPB - MarginH, PEN_BLUEDOTTED );
\r
192 else if( (index % 20) == 0 ) {
\r
193 DrawLineEx( x, MarginH, x, nHeightPB - MarginH, PEN_DOTTED );
\r
198 // made back-end by replacing MoveToEx and LineTo by DrawSegment
\r
199 /* Actually draw histogram as a diagram, cause there's too much data */
\r
200 static void DrawHistogramAsDiagram( int cy, int paint_width, int hist_count )
\r
205 /* Rescale the graph every few moves (as opposed to every move) */
\r
206 hist_count -= hist_count % 8;
\r
210 step = (double) paint_width / (hist_count + 1);
\r
212 for( i=0; i<2; i++ ) {
\r
213 int index = currFirst;
\r
214 int side = (currCurrent + i + 1) & 1; /* Draw current side last */
\r
215 double x = MarginX + MarginW;
\r
217 if( (index & 1) != side ) {
\r
222 DrawSegment( (int) x, cy, NULL, NULL, PEN_NONE );
\r
226 while( index < currLast ) {
\r
229 DrawSeparator( index, (int) x );
\r
231 /* Extend line up to current point */
\r
232 if( currPvInfo[index].depth > 0 ) {
\r
233 DrawSegment((int) x, GetValueY( GetPvScore(index) ), NULL, NULL, PEN_BOLD + side );
\r
241 // back-end, delete pen selection
\r
242 static void DrawHistogramFull( int cy, int hist_width, int hist_count )
\r
246 // SelectObject( hdcPB, GetStockObject(BLACK_PEN) );
\r
248 for( i=0; i<hist_count; i++ ) {
\r
249 int index = currFirst + i;
\r
250 int x = MarginX + MarginW + index * hist_width;
\r
252 /* Draw a separator every 10 moves */
\r
253 DrawSeparator( index, x );
\r
255 /* Draw histogram */
\r
256 if( currPvInfo[i].depth > 0 ) {
\r
257 DrawHistogram( x, cy, hist_width, GetPvScore(index), index & 1 );
\r
267 } VisualizationData;
\r
270 static Boolean InitVisualization( VisualizationData * vd )
\r
272 BOOL result = FALSE;
\r
274 vd->cy = nHeightPB / 2;
\r
275 vd->hist_width = MIN_HIST_WIDTH;
\r
276 vd->hist_count = currLast - currFirst;
\r
277 vd->paint_width = nWidthPB - MarginX - 2*MarginW;
\r
279 if( vd->hist_count > 0 ) {
\r
282 /* Compute width */
\r
283 vd->hist_width = vd->paint_width / vd->hist_count;
\r
285 if( vd->hist_width > MAX_HIST_WIDTH ) vd->hist_width = MAX_HIST_WIDTH;
\r
287 vd->hist_width -= vd->hist_width % 2;
\r
294 static void DrawHistograms()
\r
296 VisualizationData vd;
\r
298 if( InitVisualization( &vd ) ) {
\r
299 if( vd.hist_width < MIN_HIST_WIDTH ) {
\r
300 DrawHistogramAsDiagram( vd.cy, vd.paint_width, vd.hist_count );
\r
303 DrawHistogramFull( vd.cy, vd.hist_width, vd.hist_count );
\r
309 int GetMoveIndexFromPoint( int x, int y )
\r
312 int start_x = MarginX + MarginW;
\r
313 VisualizationData vd;
\r
315 if( x >= start_x && InitVisualization( &vd ) ) {
\r
316 /* Almost an hack here... we duplicate some of the paint logic */
\r
317 if( vd.hist_width < MIN_HIST_WIDTH ) {
\r
320 vd.hist_count -= vd.hist_count % 8;
\r
321 vd.hist_count += 8;
\r
322 vd.hist_count /= 2;
\r
324 step = (double) vd.paint_width / (vd.hist_count + 1);
\r
327 result = (int) (0.5 + (double) (x - start_x) / step);
\r
330 result = (x - start_x) / vd.hist_width;
\r
334 if( result >= currLast ) {
\r
341 // init and display part split of so they can be moved to front end
\r
342 void PaintEvalGraph( void )
\r
345 DrawRectangle(0, 0, nWidthPB, nHeightPB, 2, FILLED);
\r
350 Boolean EvalGraphIsUp()
\r
352 return evalGraphDialogUp;
\r
355 // ------------------------------------------ front-end starts here ----------------------------------------------
\r
357 #include <commdlg.h>
\r
360 #include "winboard.h"
\r
363 #define WM_REFRESH_GRAPH (WM_USER + 1)
\r
365 void EvalGraphSet( int first, int last, int current, ChessProgramStats_Move * pvInfo );
\r
366 void EvalGraphPopUp();
\r
367 void EvalGraphPopDown();
\r
368 Boolean EvalGraphIsUp();
\r
370 // calls of front-end part into back-end part
\r
371 extern int GetMoveIndexFromPoint( int x, int y );
\r
372 extern void PaintEvalGraph( void );
\r
374 /* Imports from winboard.c */
\r
375 extern HWND evalGraphDialog;
\r
376 extern BOOLEAN evalGraphDialogUp; // should be back-end, really
\r
378 extern HINSTANCE hInst;
\r
379 extern HWND hwndMain;
\r
381 extern WindowPlacement wpEvalGraph;
\r
383 static COLORREF crWhite = RGB( 0xFF, 0xFF, 0xB0 );
\r
384 static COLORREF crBlack = RGB( 0xAD, 0x5D, 0x3D );
\r
386 static HDC hdcPB = NULL;
\r
387 static HBITMAP hbmPB = NULL;
\r
388 static HPEN pens[6]; // [HGM] put all pens in one array
\r
389 static HBRUSH hbrHist[3] = { NULL, NULL, NULL };
\r
391 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
\r
392 static void DrawSegment( int x, int y, int *lastX, int *lastY, int penType )
\r
395 if(penType == PEN_NONE) MoveToEx( hdcPB, x, y, &stPt ); else {
\r
396 HPEN hp = SelectObject( hdcPB, pens[penType] );
\r
397 LineTo( hdcPB, x, y );
\r
398 SelectObject( hdcPB, hp );
\r
400 if(lastX != NULL) { *lastX = stPt.x; *lastY = stPt.y; }
\r
403 // front-end wrapper for drawing functions to do rectangles
\r
404 void DrawRectangle( int left, int top, int right, int bottom, int side, int style )
\r
406 HPEN hp = SelectObject( hdcPB, pens[PEN_BLACK] );
\r
409 rc.top = top; rc.left = left; rc.bottom = bottom; rc.right = right;
\r
410 if(style == FILLED)
\r
411 FillRect( hdcPB, &rc, hbrHist[side] );
\r
413 SelectObject( hdcPB, hbrHist[side] );
\r
414 Rectangle( hdcPB, left, top, right, bottom );
\r
416 SelectObject( hdcPB, hp );
\r
419 // front-end wrapper for putting text in graph
\r
420 void DrawEvalText(char *buf, int cbBuf, int y)
\r
423 SetBkMode( hdcPB, TRANSPARENT );
\r
424 GetTextExtentPoint32( hdcPB, buf, cbBuf, &stSize );
\r
425 TextOut( hdcPB, MarginX - stSize.cx - 2, y - stSize.cy / 2, buf, cbBuf );
\r
429 static HBRUSH CreateBrush( UINT style, COLORREF color )
\r
433 stLB.lbStyle = style;
\r
434 stLB.lbColor = color;
\r
437 return CreateBrushIndirect( &stLB );
\r
440 // front-end. Create pens, device context and buffer bitmap for global use, copy result to display
\r
441 // The back-end part n the middle has been taken out and moed to PainEvalGraph()
\r
442 static VOID DisplayEvalGraph( HWND hWnd, HDC hDC )
\r
448 /* Get client area */
\r
449 GetClientRect( hWnd, &rcClient );
\r
451 width = rcClient.right - rcClient.left;
\r
452 height = rcClient.bottom - rcClient.top;
\r
454 /* Create or recreate paint box if needed */
\r
455 if( hbmPB == NULL || width != nWidthPB || height != nHeightPB ) {
\r
456 if( pens[PEN_DOTTED] == NULL ) {
\r
457 pens[PEN_BLACK] = GetStockObject(BLACK_PEN);
\r
458 pens[PEN_DOTTED] = CreatePen( PS_DOT, 0, RGB(0xA0,0xA0,0xA0) );
\r
459 pens[PEN_BLUEDOTTED] = CreatePen( PS_DOT, 0, RGB(0x00,0x00,0xFF) );
\r
460 pens[PEN_BOLD] = CreatePen( PS_SOLID, 2, crWhite );
\r
461 pens[PEN_BOLD+1] = CreatePen( PS_SOLID, 2, crBlack );
\r
462 hbrHist[0] = CreateBrush( BS_SOLID, crWhite );
\r
463 hbrHist[1] = CreateBrush( BS_SOLID, crBlack );
\r
464 hbrHist[2] = CreateBrush( BS_SOLID, GetSysColor( COLOR_3DFACE ) ); // background
\r
467 if( hdcPB != NULL ) {
\r
472 if( hbmPB != NULL ) {
\r
473 DeleteObject( hbmPB );
\r
477 hdcPB = CreateCompatibleDC( hDC );
\r
480 nHeightPB = height;
\r
481 hbmPB = CreateCompatibleBitmap( hDC, nWidthPB, nHeightPB );
\r
483 SelectObject( hdcPB, hbmPB );
\r
486 // back-end painting; calls back front-end primitives for lines, rectangles and text
\r
489 /* Copy bitmap into destination DC */
\r
490 BitBlt( hDC, 0, 0, nWidthPB, nHeightPB, hdcPB, 0, 0, SRCCOPY );
\r
493 // Note: Once the eval graph is opened, this window-proc lives forever; een closing the
\r
494 // eval-graph window merely hides it. On opening we re-initialize it, though, so it could
\r
495 // as well hae been destroyed. While it is open it processes the REFRESH_GRAPH commands.
\r
496 LRESULT CALLBACK EvalGraphProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
\r
498 static SnapData sd;
\r
504 case WM_INITDIALOG:
\r
505 if( evalGraphDialog == NULL ) {
\r
506 evalGraphDialog = hDlg;
\r
508 RestoreWindowPlacement( hDlg, &wpEvalGraph ); /* Restore window placement */
\r
514 switch (LOWORD(wParam)) {
\r
516 EndDialog(hDlg, TRUE);
\r
520 EndDialog(hDlg, FALSE);
\r
529 case WM_ERASEBKGND:
\r
533 hDC = BeginPaint( hDlg, &stPS );
\r
534 DisplayEvalGraph( hDlg, hDC );
\r
535 EndPaint( hDlg, &stPS );
\r
538 case WM_REFRESH_GRAPH:
\r
539 hDC = GetDC( hDlg );
\r
540 DisplayEvalGraph( hDlg, hDC );
\r
541 ReleaseDC( hDlg, hDC );
\r
544 case WM_LBUTTONDBLCLK:
\r
545 if( wParam == 0 || wParam == MK_LBUTTON ) {
\r
546 int index = GetMoveIndexFromPoint( LOWORD(lParam), HIWORD(lParam) );
\r
548 if( index >= 0 && index < currLast ) {
\r
549 ToNrEvent( index + 1 );
\r
555 InvalidateRect( hDlg, NULL, FALSE );
\r
558 case WM_GETMINMAXINFO:
\r
560 MINMAXINFO * mmi = (MINMAXINFO *) lParam;
\r
562 mmi->ptMinTrackSize.x = 100;
\r
563 mmi->ptMinTrackSize.y = 100;
\r
567 /* Support for captionless window */
\r
569 EvalGraphPopDown();
\r
572 case WM_ENTERSIZEMOVE:
\r
573 return OnEnterSizeMove( &sd, hDlg, wParam, lParam );
\r
576 return OnSizing( &sd, hDlg, wParam, lParam );
\r
579 return OnMoving( &sd, hDlg, wParam, lParam );
\r
581 case WM_EXITSIZEMOVE:
\r
582 return OnExitSizeMove( &sd, hDlg, wParam, lParam );
\r
588 // creates the eval graph, or unhides it.
\r
589 VOID EvalGraphPopUp()
\r
593 CheckMenuItem(GetMenu(hwndMain), IDM_ShowEvalGraph, MF_CHECKED);
\r
595 if( evalGraphDialog ) {
\r
596 SendMessage( evalGraphDialog, WM_INITDIALOG, 0, 0 );
\r
598 if( ! evalGraphDialogUp ) {
\r
599 ShowWindow(evalGraphDialog, SW_SHOW);
\r
603 crWhite = appData.evalHistColorWhite;
\r
604 crBlack = appData.evalHistColorBlack;
\r
606 lpProc = MakeProcInstance( (FARPROC) EvalGraphProc, hInst );
\r
608 /* Note to self: dialog must have the WS_VISIBLE style set, otherwise it's not shown! */
\r
609 CreateDialog( hInst, MAKEINTRESOURCE(DLG_EvalGraph), hwndMain, (DLGPROC)lpProc );
\r
611 FreeProcInstance(lpProc);
\r
614 evalGraphDialogUp = TRUE;
\r
617 // Note that this hides the window. It could as well have destroyed it.
\r
618 VOID EvalGraphPopDown()
\r
620 CheckMenuItem(GetMenu(hwndMain), IDM_ShowEvalGraph, MF_UNCHECKED);
\r
622 if( evalGraphDialog ) {
\r
623 ShowWindow(evalGraphDialog, SW_HIDE);
\r
626 evalGraphDialogUp = FALSE;
\r
629 // This function is the interface to the back-end. It is currently called through the front-end,
\r
630 // though, where it shares the HistorySet() wrapper with MoveHistorySet(). Once all front-ends
\r
631 // support the eval graph, it would be more logical to call it directly from the back-end.
\r
632 VOID EvalGraphSet( int first, int last, int current, ChessProgramStats_Move * pvInfo )
\r
634 /* [AS] Danger! For now we rely on the pvInfo parameter being a static variable! */
\r
638 currCurrent = current;
\r
639 currPvInfo = pvInfo;
\r
641 if( evalGraphDialog ) {
\r
642 SendMessage( evalGraphDialog, WM_REFRESH_GRAPH, 0, 0 );
\r