21fd1e4c988b7b6a1ddef3959c15e71933cb87cc
[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 // code refactored by HGM to obtain front-end / back-end separation\r
27 \r
28 #include "config.h"\r
29 \r
30 #include <windows.h> /* required for all Windows applications */\r
31 //include <richedit.h>\r
32 #include <stdio.h>\r
33 //include <stdlib.h>\r
34 //include <malloc.h>\r
35 \r
36 #include "common.h"\r
37 #include "frontend.h"\r
38 #include "backend.h"\r
39 \r
40 /* Module globals */ // used to communicate between back-end and front-end part\r
41 static ChessProgramStats_Move * currPvInfo;\r
42 static int currFirst = 0;\r
43 static int currLast = 0;\r
44 static int currCurrent = -1;\r
45 \r
46 static int nWidthPB = 0;\r
47 static int nHeightPB = 0;\r
48 \r
49 static int MarginX = 18;\r
50 static int MarginW = 4;\r
51 static int MarginH = 4;\r
52 \r
53 #define MIN_HIST_WIDTH  4\r
54 #define MAX_HIST_WIDTH  10\r
55 \r
56 #define PEN_NONE        0\r
57 #define PEN_BLACK       1\r
58 #define PEN_DOTTED      2\r
59 #define PEN_BLUEDOTTED  3\r
60 #define PEN_BOLD        4 /* or 5 for black */\r
61 \r
62 #define FILLED 1\r
63 #define OPEN   0\r
64 \r
65 // calls from back-end part into front-end\r
66 static void DrawSegment( int x, int y, int *lastX, int *lastY, int penType );\r
67 void DrawRectangle( int left, int top, int right, int bottom, int side, int style );\r
68 void DrawEvalText(char *buf, int cbBuf, int y);\r
69 \r
70 \r
71 // back-end\r
72 static void DrawLine( int x1, int y1, int x2, int y2, int penType )\r
73 {\r
74     DrawSegment( x1, y1, NULL, NULL, PEN_NONE );\r
75     DrawSegment( x2, y2, NULL, NULL, penType );\r
76 }\r
77 \r
78 // back-end\r
79 static void DrawLineEx( int x1, int y1, int x2, int y2, int penType )\r
80 {\r
81     int savX, savY;\r
82     DrawSegment( x1, y1, &savX, &savY, PEN_NONE );\r
83     DrawSegment( x2, y2, NULL, NULL, penType );\r
84     DrawSegment( savX, savY, NULL, NULL, PEN_NONE );\r
85 }\r
86 \r
87 // back-end\r
88 static int GetPvScore( int index )\r
89 {\r
90     int score = currPvInfo[ index ].score;\r
91 \r
92     if( index & 1 ) score = -score; /* Flip score for black */\r
93 \r
94     return score;\r
95 }\r
96 \r
97 // back-end\r
98 /*\r
99     For a centipawn value, this function returns the height of the corresponding\r
100     histogram, centered on the reference axis.\r
101 \r
102     Note: height can be negative!\r
103 */\r
104 static int GetValueY( int value )\r
105 {\r
106     if( value < -700 ) value = -700;\r
107     if( value > +700 ) value = +700;\r
108 \r
109     return (nHeightPB / 2) - (int)(value * (nHeightPB - 2*MarginH) / 1400.0);\r
110 }\r
111 \r
112 // the brush selection is made part of the DrawLine, by passing a style argument\r
113 // the wrapper for doing the text output makes this back-end\r
114 static void DrawAxisSegmentHoriz( int value, BOOL drawValue )\r
115 {\r
116     int y = GetValueY( value*100 );\r
117 \r
118     if( drawValue ) {\r
119         char buf[MSG_SIZ], *b = buf;\r
120 \r
121         if( value > 0 ) *b++ = '+';\r
122         sprintf(b, "%d", value);\r
123 \r
124         DrawEvalText(buf, strlen(buf), y);\r
125     }\r
126     // [HGM] counts on DrawEvalText to have select transparent background for dotted line!\r
127     DrawLine( MarginX, y, MarginX + MarginW, y, PEN_BLACK ); // Y-axis tick marks\r
128     DrawLine( MarginX + MarginW, y, nWidthPB - MarginW, y, PEN_DOTTED ); // hor grid\r
129 }\r
130 \r
131 // The DrawLines again must select their own brush.\r
132 // the initial brush selection is useless? BkMode needed for dotted line and text\r
133 static void DrawAxis()\r
134 {\r
135     int cy = nHeightPB / 2;\r
136     \r
137 //    SelectObject( hdcPB, GetStockObject(NULL_BRUSH) );\r
138 \r
139 //    SetBkMode( hdcPB, TRANSPARENT );\r
140 \r
141     DrawAxisSegmentHoriz( +5, TRUE );\r
142     DrawAxisSegmentHoriz( +3, FALSE );\r
143     DrawAxisSegmentHoriz( +1, FALSE );\r
144     DrawAxisSegmentHoriz(  0, TRUE );\r
145     DrawAxisSegmentHoriz( -1, FALSE );\r
146     DrawAxisSegmentHoriz( -3, FALSE );\r
147     DrawAxisSegmentHoriz( -5, TRUE );\r
148 \r
149     DrawLine( MarginX + MarginW, cy, nWidthPB - MarginW, cy, PEN_BLACK ); // x-axis\r
150     DrawLine( MarginX + MarginW, MarginH, MarginX + MarginW, nHeightPB - MarginH, PEN_BLACK ); // y-axis\r
151 }\r
152 \r
153 // back-end\r
154 static void DrawHistogram( int x, int y, int width, int value, int side )\r
155 {\r
156     int left, top, right, bottom;\r
157 \r
158     if( value > -25 && value < +25 ) return;\r
159 \r
160     left = x;\r
161     right = left + width + 1;\r
162 \r
163     if( value > 0 ) {\r
164         top = GetValueY( value );\r
165         bottom = y+1;\r
166     }\r
167     else {\r
168         top = y;\r
169         bottom = GetValueY( value ) + 1;\r
170     }\r
171 \r
172 \r
173     if( width == MIN_HIST_WIDTH ) {\r
174         right--;\r
175         DrawRectangle( left, top, right, bottom, side, FILLED );\r
176     }\r
177     else {\r
178         DrawRectangle( left, top, right, bottom, side, OPEN );\r
179     }\r
180 }\r
181 \r
182 // back-end\r
183 static void DrawSeparator( int index, int x )\r
184 {\r
185     if( index > 0 ) {\r
186         if( index == currCurrent ) {\r
187             DrawLineEx( x, MarginH, x, nHeightPB - MarginH, PEN_BLUEDOTTED );\r
188         }\r
189         else if( (index % 20) == 0 ) {\r
190             DrawLineEx( x, MarginH, x, nHeightPB - MarginH, PEN_DOTTED );\r
191         }\r
192     }\r
193 }\r
194 \r
195 // made back-end by replacing MoveToEx and LineTo by DrawSegment\r
196 /* Actually draw histogram as a diagram, cause there's too much data */\r
197 static void DrawHistogramAsDiagram( int cy, int paint_width, int hist_count )\r
198 {\r
199     double step;\r
200     int i;\r
201 \r
202     /* Rescale the graph every few moves (as opposed to every move) */\r
203     hist_count -= hist_count % 8;\r
204     hist_count += 8;\r
205     hist_count /= 2;\r
206 \r
207     step = (double) paint_width / (hist_count + 1);\r
208 \r
209     for( i=0; i<2; i++ ) {\r
210         int index = currFirst;\r
211         int side = (currCurrent + i + 1) & 1; /* Draw current side last */\r
212         double x = MarginX + MarginW;\r
213 \r
214         if( (index & 1) != side ) {\r
215             x += step / 2;\r
216             index++;\r
217         }\r
218 \r
219         DrawSegment( (int) x, cy, NULL, NULL, PEN_NONE );\r
220 \r
221         index += 2;\r
222 \r
223         while( index < currLast ) {\r
224             x += step;\r
225 \r
226             DrawSeparator( index, (int) x );\r
227 \r
228             /* Extend line up to current point */\r
229             if( currPvInfo[index].depth > 0 ) {\r
230                 DrawSegment((int) x, GetValueY( GetPvScore(index) ), NULL, NULL, PEN_BOLD + side );\r
231             }\r
232 \r
233             index += 2;\r
234         }\r
235     }\r
236 }\r
237 \r
238 // back-end, delete pen selection\r
239 static void DrawHistogramFull( int cy, int hist_width, int hist_count )\r
240 {\r
241     int i;\r
242 \r
243 //    SelectObject( hdcPB, GetStockObject(BLACK_PEN) );\r
244 \r
245     for( i=0; i<hist_count; i++ ) {\r
246         int index = currFirst + i;\r
247         int x = MarginX + MarginW + index * hist_width;\r
248 \r
249         /* Draw a separator every 10 moves */\r
250         DrawSeparator( index, x );\r
251 \r
252         /* Draw histogram */\r
253         if( currPvInfo[i].depth > 0 ) {\r
254             DrawHistogram( x, cy, hist_width, GetPvScore(index), index & 1 );\r
255         }\r
256     }\r
257 }\r
258 \r
259 typedef struct {\r
260     int cy;\r
261     int hist_width;\r
262     int hist_count;\r
263     int paint_width;\r
264 } VisualizationData;\r
265 \r
266 // back-end\r
267 static Boolean InitVisualization( VisualizationData * vd )\r
268 {\r
269     BOOL result = FALSE;\r
270 \r
271     vd->cy = nHeightPB / 2;\r
272     vd->hist_width = MIN_HIST_WIDTH;\r
273     vd->hist_count = currLast - currFirst;\r
274     vd->paint_width = nWidthPB - MarginX - 2*MarginW;\r
275 \r
276     if( vd->hist_count > 0 ) {\r
277         result = TRUE;\r
278 \r
279         /* Compute width */\r
280         vd->hist_width = vd->paint_width / vd->hist_count;\r
281 \r
282         if( vd->hist_width > MAX_HIST_WIDTH ) vd->hist_width = MAX_HIST_WIDTH;\r
283 \r
284         vd->hist_width -= vd->hist_width % 2;\r
285     }\r
286 \r
287     return result;\r
288 }\r
289 \r
290 // back-end\r
291 static void DrawHistograms()\r
292 {\r
293     VisualizationData vd;\r
294 \r
295     if( InitVisualization( &vd ) ) {\r
296         if( vd.hist_width < MIN_HIST_WIDTH ) {\r
297             DrawHistogramAsDiagram( vd.cy, vd.paint_width, vd.hist_count );\r
298         }\r
299         else {\r
300             DrawHistogramFull( vd.cy, vd.hist_width, vd.hist_count );\r
301         }\r
302     }\r
303 }\r
304 \r
305 // back-end\r
306 int GetMoveIndexFromPoint( int x, int y )\r
307 {\r
308     int result = -1;\r
309     int start_x = MarginX + MarginW;\r
310     VisualizationData vd;\r
311 \r
312     if( x >= start_x && InitVisualization( &vd ) ) {\r
313         /* Almost an hack here... we duplicate some of the paint logic */\r
314         if( vd.hist_width < MIN_HIST_WIDTH ) {\r
315             double step;\r
316 \r
317             vd.hist_count -= vd.hist_count % 8;\r
318             vd.hist_count += 8;\r
319             vd.hist_count /= 2;\r
320 \r
321             step = (double) vd.paint_width / (vd.hist_count + 1);\r
322             step /= 2;\r
323 \r
324             result = (int) (0.5 + (double) (x - start_x) / step);\r
325         }\r
326         else {\r
327             result = (x - start_x) / vd.hist_width;\r
328         }\r
329     }\r
330 \r
331     if( result >= currLast ) {\r
332         result = -1;\r
333     }\r
334 \r
335     return result;\r
336 }\r
337 \r
338 // init and display part split of so they can be moved to front end\r
339 void PaintEvalGraph( void )\r
340 {\r
341     /* Draw */\r
342     DrawRectangle(0, 0, nWidthPB, nHeightPB, 2, FILLED);\r
343     DrawAxis();\r
344     DrawHistograms();\r
345 }\r
346 \r
347 // ------------------------------------------ front-end starts here ----------------------------------------------\r
348 \r
349 #include <commdlg.h>\r
350 #include <dlgs.h>\r
351 \r
352 #include "winboard.h"\r
353 #include "wsnap.h"\r
354 \r
355 #define WM_REFRESH_GRAPH    (WM_USER + 1)\r
356 \r
357 // calls of front-end part into back-end part\r
358 extern int GetMoveIndexFromPoint( int x, int y );\r
359 extern void PaintEvalGraph( void );\r
360 \r
361 /* Imports from winboard.c */\r
362 static BOOLEAN evalGraphDialogUp; // should be back-end, really\r
363 \r
364 extern HINSTANCE hInst;\r
365 extern HWND hwndMain;\r
366 \r
367 extern WindowPlacement wpEvalGraph;\r
368 \r
369 static COLORREF crWhite = RGB( 0xFF, 0xFF, 0xB0 );\r
370 static COLORREF crBlack = RGB( 0xAD, 0x5D, 0x3D );\r
371 \r
372 static HDC hdcPB = NULL;\r
373 static HBITMAP hbmPB = NULL;\r
374 static HPEN pens[6]; // [HGM] put all pens in one array\r
375 static HBRUSH hbrHist[3] = { NULL, NULL, NULL };\r
376 \r
377 Boolean EvalGraphIsUp()\r
378 {\r
379     return evalGraphDialogUp;\r
380 }\r
381 \r
382 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end) \r
383 static void DrawSegment( int x, int y, int *lastX, int *lastY, int penType )\r
384 {\r
385     POINT stPt;\r
386     if(penType == PEN_NONE) MoveToEx( hdcPB, x, y, &stPt ); else {\r
387         HPEN hp = SelectObject( hdcPB, pens[penType] );\r
388         LineTo( hdcPB, x, y );\r
389         SelectObject( hdcPB, hp );\r
390     }\r
391     if(lastX != NULL) { *lastX = stPt.x; *lastY = stPt.y; }\r
392 }\r
393 \r
394 // front-end wrapper for drawing functions to do rectangles\r
395 void DrawRectangle( int left, int top, int right, int bottom, int side, int style )\r
396 {\r
397     HPEN hp = SelectObject( hdcPB, pens[PEN_BLACK] );\r
398     RECT rc;\r
399 \r
400     rc.top = top; rc.left = left; rc.bottom = bottom; rc.right = right;\r
401     if(style == FILLED)\r
402         FillRect( hdcPB, &rc, hbrHist[side] );\r
403     else {\r
404         SelectObject( hdcPB, hbrHist[side] );\r
405         Rectangle( hdcPB, left, top, right, bottom );\r
406     }\r
407     SelectObject( hdcPB, hp );\r
408 }\r
409 \r
410 // front-end wrapper for putting text in graph\r
411 void DrawEvalText(char *buf, int cbBuf, int y)\r
412 {\r
413         SIZE stSize;\r
414         SetBkMode( hdcPB, TRANSPARENT );\r
415         GetTextExtentPoint32( hdcPB, buf, cbBuf, &stSize );\r
416         TextOut( hdcPB, MarginX - stSize.cx - 2, y - stSize.cy / 2, buf, cbBuf );\r
417 }\r
418 \r
419 // front-end\r
420 static HBRUSH CreateBrush( UINT style, COLORREF color )\r
421 {\r
422     LOGBRUSH stLB;\r
423 \r
424     stLB.lbStyle = style;\r
425     stLB.lbColor = color;\r
426     stLB.lbHatch = 0;\r
427 \r
428     return CreateBrushIndirect( &stLB );\r
429 }\r
430 \r
431 // front-end. Create pens, device context and buffer bitmap for global use, copy result to display\r
432 // The back-end part n the middle has been taken out and moed to PainEvalGraph()\r
433 static VOID DisplayEvalGraph( HWND hWnd, HDC hDC )\r
434 {\r
435     RECT rcClient;\r
436     int width;\r
437     int height;\r
438 \r
439     /* Get client area */\r
440     GetClientRect( hWnd, &rcClient );\r
441 \r
442     width = rcClient.right - rcClient.left;\r
443     height = rcClient.bottom - rcClient.top;\r
444 \r
445     /* Create or recreate paint box if needed */\r
446     if( hbmPB == NULL || width != nWidthPB || height != nHeightPB ) {\r
447         if( pens[PEN_DOTTED] == NULL ) {\r
448             pens[PEN_BLACK]     = GetStockObject(BLACK_PEN);\r
449             pens[PEN_DOTTED]    = CreatePen( PS_DOT, 0, RGB(0xA0,0xA0,0xA0) );\r
450             pens[PEN_BLUEDOTTED] = CreatePen( PS_DOT, 0, RGB(0x00,0x00,0xFF) );\r
451             pens[PEN_BOLD]      = CreatePen( PS_SOLID, 2, crWhite );\r
452             pens[PEN_BOLD+1]    = CreatePen( PS_SOLID, 2, crBlack );\r
453             hbrHist[0] = CreateBrush( BS_SOLID, crWhite );\r
454             hbrHist[1] = CreateBrush( BS_SOLID, crBlack );\r
455             hbrHist[2] = CreateBrush( BS_SOLID, GetSysColor( COLOR_3DFACE ) ); // background\r
456         }\r
457 \r
458         if( hdcPB != NULL ) {\r
459             DeleteDC( hdcPB );\r
460             hdcPB = NULL;\r
461         }\r
462 \r
463         if( hbmPB != NULL ) {\r
464             DeleteObject( hbmPB );\r
465             hbmPB = NULL;\r
466         }\r
467 \r
468         hdcPB = CreateCompatibleDC( hDC );\r
469 \r
470         nWidthPB = width;\r
471         nHeightPB = height;\r
472         hbmPB = CreateCompatibleBitmap( hDC, nWidthPB, nHeightPB );\r
473 \r
474         SelectObject( hdcPB, hbmPB );\r
475     }\r
476 \r
477     // back-end painting; calls back front-end primitives for lines, rectangles and text\r
478     PaintEvalGraph();\r
479 \r
480     /* Copy bitmap into destination DC */\r
481     BitBlt( hDC, 0, 0, nWidthPB, nHeightPB, hdcPB, 0, 0, SRCCOPY );\r
482 }\r
483 \r
484 // Note: Once the eval graph is opened, this window-proc lives forever; een closing the\r
485 // eval-graph window merely hides it. On opening we re-initialize it, though, so it could\r
486 // as well hae been destroyed. While it is open it processes the REFRESH_GRAPH commands.\r
487 LRESULT CALLBACK EvalGraphProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )\r
488 {\r
489     static SnapData sd;\r
490 \r
491     PAINTSTRUCT stPS;\r
492     HDC hDC;\r
493 \r
494     switch (message) {\r
495     case WM_INITDIALOG:\r
496         if( evalGraphDialog == NULL ) {\r
497             evalGraphDialog = hDlg;\r
498 \r
499             RestoreWindowPlacement( hDlg, &wpEvalGraph ); /* Restore window placement */\r
500         }\r
501 \r
502         return FALSE;\r
503 \r
504     case WM_COMMAND:\r
505         switch (LOWORD(wParam)) {\r
506         case IDOK:\r
507           EndDialog(hDlg, TRUE);\r
508           return TRUE;\r
509 \r
510         case IDCANCEL:\r
511           EndDialog(hDlg, FALSE);\r
512           return TRUE;\r
513 \r
514         default:\r
515           break;\r
516         }\r
517 \r
518         break;\r
519 \r
520     case WM_ERASEBKGND:\r
521         return TRUE;\r
522 \r
523     case WM_PAINT:\r
524         hDC = BeginPaint( hDlg, &stPS );\r
525         DisplayEvalGraph( hDlg, hDC );\r
526         EndPaint( hDlg, &stPS );\r
527         break;\r
528 \r
529     case WM_REFRESH_GRAPH:\r
530         hDC = GetDC( hDlg );\r
531         DisplayEvalGraph( hDlg, hDC );\r
532         ReleaseDC( hDlg, hDC );\r
533         break;\r
534 \r
535     case WM_LBUTTONDBLCLK:\r
536         if( wParam == 0 || wParam == MK_LBUTTON ) {\r
537             int index = GetMoveIndexFromPoint( LOWORD(lParam), HIWORD(lParam) );\r
538 \r
539             if( index >= 0 && index < currLast ) {\r
540                 ToNrEvent( index + 1 );\r
541             }\r
542         }\r
543         return TRUE;\r
544 \r
545     case WM_SIZE:\r
546         InvalidateRect( hDlg, NULL, FALSE );\r
547         break;\r
548 \r
549     case WM_GETMINMAXINFO:\r
550         {\r
551             MINMAXINFO * mmi = (MINMAXINFO *) lParam;\r
552         \r
553             mmi->ptMinTrackSize.x = 100;\r
554             mmi->ptMinTrackSize.y = 100;\r
555         }\r
556         break;\r
557 \r
558     /* Support for captionless window */\r
559     case WM_CLOSE:\r
560         EvalGraphPopDown();\r
561         break;\r
562 \r
563     case WM_ENTERSIZEMOVE:\r
564         return OnEnterSizeMove( &sd, hDlg, wParam, lParam );\r
565 \r
566     case WM_SIZING:\r
567         return OnSizing( &sd, hDlg, wParam, lParam );\r
568 \r
569     case WM_MOVING:\r
570         return OnMoving( &sd, hDlg, wParam, lParam );\r
571 \r
572     case WM_EXITSIZEMOVE:\r
573         return OnExitSizeMove( &sd, hDlg, wParam, lParam );\r
574     }\r
575 \r
576     return FALSE;\r
577 }\r
578 \r
579 // creates the eval graph, or unhides it.\r
580 VOID EvalGraphPopUp()\r
581 {\r
582   FARPROC lpProc;\r
583   \r
584   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEvalGraph, MF_CHECKED);\r
585 \r
586   if( evalGraphDialog ) {\r
587     SendMessage( evalGraphDialog, WM_INITDIALOG, 0, 0 );\r
588 \r
589     if( ! evalGraphDialogUp ) {\r
590         ShowWindow(evalGraphDialog, SW_SHOW);\r
591     }\r
592   }\r
593   else {\r
594     crWhite = appData.evalHistColorWhite;\r
595     crBlack = appData.evalHistColorBlack;\r
596 \r
597     lpProc = MakeProcInstance( (FARPROC) EvalGraphProc, hInst );\r
598 \r
599     /* Note to self: dialog must have the WS_VISIBLE style set, otherwise it's not shown! */\r
600     CreateDialog( hInst, MAKEINTRESOURCE(DLG_EvalGraph), hwndMain, (DLGPROC)lpProc );\r
601 \r
602     FreeProcInstance(lpProc);\r
603   }\r
604 \r
605   evalGraphDialogUp = TRUE;\r
606 }\r
607 \r
608 // Note that this hides the window. It could as well have destroyed it.\r
609 VOID EvalGraphPopDown()\r
610 {\r
611   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEvalGraph, MF_UNCHECKED);\r
612 \r
613   if( evalGraphDialog ) {\r
614       ShowWindow(evalGraphDialog, SW_HIDE);\r
615   }\r
616 \r
617   evalGraphDialogUp = FALSE;\r
618 }\r
619 \r
620 // This function is the interface to the back-end. It is currently called through the front-end,\r
621 // though, where it shares the HistorySet() wrapper with MoveHistorySet(). Once all front-ends\r
622 // support the eval graph, it would be more logical to call it directly from the back-end.\r
623 VOID EvalGraphSet( int first, int last, int current, ChessProgramStats_Move * pvInfo )\r
624 {\r
625     /* [AS] Danger! For now we rely on the pvInfo parameter being a static variable! */\r
626 \r
627     currFirst = first;\r
628     currLast = last;\r
629     currCurrent = current;\r
630     currPvInfo = pvInfo;\r
631 \r
632     if( evalGraphDialog ) {\r
633         SendMessage( evalGraphDialog, WM_REFRESH_GRAPH, 0, 0 );\r
634     }\r
635 }\r
636 \r