changes from Alessandro Scotti from 20060112
[xboard.git] / winboard / wengineoutput.c
1 /*
2  * Engine output (PV)
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 EngineOutputPopUp();
40 VOID EngineOutputPopDown();
41 BOOL EngineOutputIsUp();
42
43 #define SHOW_PONDERING
44
45 /* Imports from backend.c */
46 char * SavePart(char *str);
47
48 /* Imports from winboard.c */
49 extern HWND engineOutputDialog;
50 extern BOOLEAN engineOutputDialogUp;
51
52 extern HINSTANCE hInst;
53 extern HWND hwndMain;
54
55 extern WindowPlacement wpEngineOutput;
56
57 /* Module variables */
58 #define H_MARGIN            2
59 #define V_MARGIN            2
60 #define LABEL_V_DISTANCE    1   /* Distance between label and memo */
61 #define SPLITTER_SIZE       4   /* Distance between first memo and second label */
62
63 #define ICON_SIZE           14
64
65 #define STATE_UNKNOWN   -1
66 #define STATE_THINKING   0
67 #define STATE_IDLE       1
68 #define STATE_PONDERING  2
69
70 static int  windowMode = 1;
71
72 static BOOL needInit = TRUE;
73
74 static HICON hiColorBlack = NULL;
75 static HICON hiColorWhite = NULL;
76 static HICON hiColorUnknown = NULL;
77 static HICON hiClear = NULL;
78 static HICON hiPondering = NULL;
79 static HICON hiThinking = NULL;
80
81 static int  lastDepth[2] = { -1, -1 };
82 static int  lastForwardMostMove[2] = { -1, -1 };
83 static int  engineState[2] = { -1, -1 };
84
85 typedef struct {
86     int which;
87     HWND hColorIcon;
88     HWND hLabel;
89     HWND hStateIcon;
90     HWND hStateData;
91     HWND hLabelNPS;
92     HWND hMemo;
93     char * name;
94     int depth;
95     unsigned long nodes;
96     int score;
97     int time;
98     char * pv;
99     char * hint;
100 } EngineOutputData;
101
102 static HICON LoadIconEx( int id )
103 {
104     return LoadImage( hInst, MAKEINTRESOURCE(id), IMAGE_ICON, ICON_SIZE, ICON_SIZE, 0 );
105 }
106
107 static VOID InitializeEngineOutput()
108 {
109     if( needInit ) {
110         hiColorBlack = LoadIconEx( IDI_BLACK_14 );
111         hiColorWhite = LoadIconEx( IDI_WHITE_14 );
112         hiColorUnknown = LoadIconEx( IDI_UNKNOWN_14 );
113         hiClear = LoadIconEx( IDI_TRANS_14 );
114         hiPondering = LoadIconEx( IDI_PONDER_14 );
115         hiThinking = LoadIconEx( IDI_CLOCK_14 );
116         needInit = FALSE;
117     }
118 }
119
120 static VOID SetControlPos( HWND hDlg, int id, int x, int y, int width, int height )
121 {
122     HWND hControl = GetDlgItem( hDlg, id );
123
124     SetWindowPos( hControl, HWND_TOP, x, y, width, height, SWP_NOZORDER );
125 }
126
127 #define HIDDEN_X    20000
128 #define HIDDEN_Y    20000
129
130 static VOID HideControl( HWND hDlg, int id )
131 {
132     HWND hControl = GetDlgItem( hDlg, id );
133     RECT rc;
134
135     GetWindowRect( hControl, &rc );
136
137     /*
138         Avoid hiding an already hidden control, because that causes many
139         unnecessary WM_ERASEBKGND messages!
140     */
141     if( rc.left != HIDDEN_X || rc.top != HIDDEN_Y ) {
142     SetControlPos( hDlg, id, 20000, 20000, 100, 100 );
143     }
144 }
145
146 static int GetControlWidth( HWND hDlg, int id )
147 {
148     RECT rc;
149
150     GetWindowRect( GetDlgItem( hDlg, id ), &rc );
151
152     return rc.right - rc.left;
153 }
154
155 static int GetControlHeight( HWND hDlg, int id )
156 {
157     RECT rc;
158
159     GetWindowRect( GetDlgItem( hDlg, id ), &rc );
160
161     return rc.bottom - rc.top;
162 }
163
164 static int GetHeaderHeight()
165 {
166     int result = GetControlHeight( engineOutputDialog, IDC_EngineLabel1 );
167
168     if( result < ICON_SIZE ) result = ICON_SIZE;
169
170     return result;
171 }
172
173 #define ENGINE_COLOR_WHITE      'w'
174 #define ENGINE_COLOR_BLACK      'b'
175 #define ENGINE_COLOR_UNKNOWN    ' '
176
177 char GetEngineColor( int which )
178 {
179     char result = ENGINE_COLOR_UNKNOWN;
180
181     if( which == 0 || which == 1 ) {
182         ChessProgramState * cps;
183
184         switch (gameMode) {
185         case MachinePlaysBlack:
186         case IcsPlayingBlack:
187             result = ENGINE_COLOR_BLACK;
188             break;
189         case MachinePlaysWhite:
190         case IcsPlayingWhite:
191             result = ENGINE_COLOR_WHITE;
192             break;
193         case AnalyzeMode:
194         case AnalyzeFile:
195             result = WhiteOnMove(forwardMostMove) ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;
196             break;
197         case TwoMachinesPlay:
198             cps = (which == 0) ? &first : &second;
199             result = cps->twoMachinesColor[0];
200             result = result == 'w' ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;
201             break;
202         }
203     }
204
205     return result;
206 }
207
208 char GetActiveEngineColor()
209 {
210     char result = ENGINE_COLOR_UNKNOWN;
211
212     if( gameMode == TwoMachinesPlay ) {
213         result = WhiteOnMove(forwardMostMove) ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;
214     }
215
216     return result;
217 }
218
219 static VOID PositionControlSet( HWND hDlg, int x, int y, int clientWidth, int memoHeight, int idColor, int idEngineLabel, int idNPS, int idMemo, int idStateIcon, int idStateData )
220 {
221     int label_x = x + ICON_SIZE + H_MARGIN;
222     int label_h = GetControlHeight( hDlg, IDC_EngineLabel1 );
223     int label_y = y + ICON_SIZE - label_h;
224     int nps_w = GetControlWidth( hDlg, IDC_Engine1_NPS );
225     int nps_x = clientWidth - H_MARGIN - nps_w;
226     int state_data_w = GetControlWidth( hDlg, IDC_StateData1 );
227     int state_data_x = nps_x - H_MARGIN - state_data_w;
228     int state_icon_x = state_data_x - ICON_SIZE - 2;
229     int max_w = clientWidth - 2*H_MARGIN;
230     int memo_y = y + ICON_SIZE + LABEL_V_DISTANCE;
231
232     SetControlPos( hDlg, idColor, x, y, ICON_SIZE, ICON_SIZE );
233     SetControlPos( hDlg, idEngineLabel, label_x, label_y, max_w / 2, label_h );
234     SetControlPos( hDlg, idStateIcon, state_icon_x, y, ICON_SIZE, ICON_SIZE );
235     SetControlPos( hDlg, idStateData, state_data_x, label_y, state_data_w, label_h );
236     SetControlPos( hDlg, idNPS, nps_x, label_y, nps_w, label_h );
237     SetControlPos( hDlg, idMemo, x, memo_y, max_w, memoHeight );
238 }
239
240 static VOID ResizeWindowControls( HWND hDlg, int mode )
241 {
242     RECT rc;
243     int headerHeight = GetHeaderHeight();
244     int labelHeight = GetControlHeight( hDlg, IDC_EngineLabel1 );
245     int labelOffset = H_MARGIN + ICON_SIZE + H_MARGIN;
246     int labelDeltaY = ICON_SIZE - labelHeight;
247     int clientWidth;
248     int clientHeight;
249     int maxControlWidth;
250     int npsWidth;
251
252     /* Initialize variables */
253     GetClientRect( hDlg, &rc );
254
255     clientWidth = rc.right - rc.left;
256     clientHeight = rc.bottom - rc.top;
257
258     maxControlWidth = clientWidth - 2*H_MARGIN;
259
260     npsWidth = GetControlWidth( hDlg, IDC_Engine1_NPS );
261
262     /* Resize controls */
263     if( mode == 0 ) {
264         /* One engine */
265         PositionControlSet( hDlg, H_MARGIN, V_MARGIN,
266             clientWidth,
267             clientHeight - V_MARGIN - LABEL_V_DISTANCE - headerHeight- V_MARGIN,
268             IDC_Color1, IDC_EngineLabel1, IDC_Engine1_NPS, IDC_EngineMemo1, IDC_StateIcon1, IDC_StateData1 );
269
270         /* Hide controls for the second engine */
271         HideControl( hDlg, IDC_Color2 );
272         HideControl( hDlg, IDC_EngineLabel2 );
273         HideControl( hDlg, IDC_StateIcon2 );
274         HideControl( hDlg, IDC_StateData2 );
275         HideControl( hDlg, IDC_Engine2_NPS );
276         HideControl( hDlg, IDC_EngineMemo2 );
277         SendDlgItemMessage( hDlg, IDC_EngineMemo2, WM_SETTEXT, 0, (LPARAM) "" );
278         /* TODO: we should also hide/disable them!!! what about tab stops?!?! */
279     }
280     else {
281         /* Two engines */
282         int memo_h = (clientHeight - headerHeight*2 - V_MARGIN*2 - LABEL_V_DISTANCE*2 - SPLITTER_SIZE) / 2;
283         int header1_y = V_MARGIN;
284         int header2_y = V_MARGIN + headerHeight + LABEL_V_DISTANCE + memo_h + SPLITTER_SIZE;
285
286         PositionControlSet( hDlg, H_MARGIN, header1_y, clientWidth, memo_h,
287             IDC_Color1, IDC_EngineLabel1, IDC_Engine1_NPS, IDC_EngineMemo1, IDC_StateIcon1, IDC_StateData1 );
288
289         PositionControlSet( hDlg, H_MARGIN, header2_y, clientWidth, memo_h,
290             IDC_Color2, IDC_EngineLabel2, IDC_Engine2_NPS, IDC_EngineMemo2, IDC_StateIcon2, IDC_StateData2 );
291     }
292
293     InvalidateRect( GetDlgItem(hDlg,IDC_EngineMemo1), NULL, FALSE );
294     InvalidateRect( GetDlgItem(hDlg,IDC_EngineMemo2), NULL, FALSE );
295 }
296
297 static VOID SetDisplayMode( int mode )
298 {
299     if( windowMode != mode ) {
300         windowMode = mode;
301
302         ResizeWindowControls( engineOutputDialog, mode );
303     }
304 }
305
306 static VOID VerifyDisplayMode()
307 {
308     int mode;
309
310     /* Get proper mode for current game */
311     switch( gameMode ) {
312     case AnalyzeMode:
313     case AnalyzeFile:
314     case MachinePlaysWhite:
315     case MachinePlaysBlack:
316     case IcsPlayingWhite:
317     case IcsPlayingBlack:
318         mode = 0;
319         break;
320     case TwoMachinesPlay:
321         mode = 1;
322         break;
323     default:
324         /* Do not change */
325         return;
326     }
327
328     SetDisplayMode( mode );
329 }
330
331 static VOID InsertIntoMemo( HWND hMemo, char * text )
332 {
333     SendMessage( hMemo, EM_SETSEL, 0, 0 );
334
335     SendMessage( hMemo, EM_REPLACESEL, (WPARAM) FALSE, (LPARAM) text );
336 }
337
338 static VOID SetIcon( HWND hControl, HICON hIcon )
339 {
340     if( hIcon != NULL ) {
341         SendMessage( hControl, STM_SETICON, (WPARAM) hIcon, 0 );
342     }
343 }
344
345 static VOID SetEngineColorIcon( HWND hControl, int which )
346 {
347     char color = GetEngineColor(which);
348     HICON hicon = NULL;
349
350     if( color == ENGINE_COLOR_BLACK )
351         hicon = hiColorBlack;
352     else if( color == ENGINE_COLOR_WHITE )
353         hicon = hiColorWhite;
354     else
355         hicon = hiColorUnknown;
356
357     SetIcon( hControl, hicon );
358 }
359
360 static SetEngineState( int which, int state, char * state_data )
361 {
362     int x_which = 1 - which;
363     HWND hStateIcon = GetDlgItem( engineOutputDialog, which == 0 ? IDC_StateIcon1 : IDC_StateIcon2 );
364     HWND hStateData = GetDlgItem( engineOutputDialog, which == 0 ? IDC_StateData1 : IDC_StateData2 );
365
366     if( engineState[ which ] != state ) {
367         engineState[ which ] = state;
368
369         switch( state ) {
370         case STATE_THINKING:
371             SetIcon( hStateIcon, hiThinking );
372             if( engineState[ x_which ] == STATE_THINKING ) {
373                 SetEngineState( x_which, STATE_IDLE, "" );
374             }
375             break;
376         case STATE_PONDERING:
377             SetIcon( hStateIcon, hiPondering );
378             break;
379         default:
380             SetIcon( hStateIcon, hiClear );
381             break;
382         }
383     }
384
385     if( state_data != 0 ) {
386         SetWindowText( hStateData, state_data );
387     }
388 }
389
390 #define MAX_NAME_LENGTH 32
391
392 static VOID UpdateControls( EngineOutputData * ed )
393 {
394     char s_label[MAX_NAME_LENGTH + 32];
395
396     char * name = ed->name;
397
398     /* Label */
399     if( name == 0 || *name == '\0' ) {
400         name = "?";
401     }
402
403     strncpy( s_label, name, MAX_NAME_LENGTH );
404     s_label[ MAX_NAME_LENGTH-1 ] = '\0';
405
406 #ifdef SHOW_PONDERING
407     if( GetActiveEngineColor() != ENGINE_COLOR_UNKNOWN ) {
408         if( GetEngineColor(ed->which) != GetActiveEngineColor() ) {
409             char buf[8];
410
411             buf[0] = '\0';
412
413             if( ed->hint != 0 && *ed->hint != '\0' ) {
414                 strncpy( buf, ed->hint, sizeof(buf) );
415                 buf[sizeof(buf)-1] = '\0';
416             }
417             else if( ed->pv != 0 && *ed->pv != '\0' ) {
418                 char * sep = strchr( ed->pv, ' ' );
419                 int buflen = sizeof(buf);
420
421                 if( sep != NULL ) {
422                     buflen = sep - ed->pv + 1;
423                     if( buflen > sizeof(buf) ) buflen = sizeof(buf);
424                 }
425
426                 strncpy( buf, ed->pv, buflen );
427                 buf[ buflen-1 ] = '\0';
428             }
429
430             SetEngineState( ed->which, STATE_PONDERING, buf );
431         }
432         else {
433             SetEngineState( ed->which, STATE_THINKING, "" );
434         }
435     }
436     else {
437         SetEngineState( ed->which, STATE_IDLE, "" );
438     }
439 #endif
440
441     SetWindowText( ed->hLabel, s_label );
442
443     s_label[0] = '\0';
444
445     if( ed->time > 0 && ed->nodes > 0 ) {
446         unsigned long nps_100 = ed->nodes / ed->time;
447
448         if( nps_100 < 100000 ) {
449             sprintf( s_label, "NPS: %lu", nps_100 * 100 );
450         }
451         else {
452             sprintf( s_label, "NPS: %.1fk", nps_100 / 10.0 );
453         }
454     }
455
456     SetWindowText( ed->hLabelNPS, s_label );
457
458     /* Memo */
459     if( ed->pv != 0 && *ed->pv != '\0' ) {
460         char s_nodes[24];
461         char s_score[16];
462         char s_time[24];
463         char buf[256];
464         int buflen;
465         int time_secs = ed->time / 100;
466         int time_cent = ed->time % 100;
467
468         /* Nodes */
469         if( ed->nodes < 1000000 ) {
470             sprintf( s_nodes, "%lu", ed->nodes );
471         }
472         else {
473             sprintf( s_nodes, "%.1fM", ed->nodes / 1000000.0 );
474         }
475
476         /* Score */
477         if( ed->score > 0 ) {
478             sprintf( s_score, "+%.2f", ed->score / 100.0 );
479         }
480         else {
481             sprintf( s_score, "%.2f", ed->score / 100.0 );
482         }
483
484         /* Time */
485         sprintf( s_time, "%d:%02d.%02d", time_secs / 60, time_secs % 60, time_cent );
486
487         /* Put all together... */
488         sprintf( buf, "%3d\t%s\t%s\t%s\t", ed->depth, s_score, s_nodes, s_time );
489
490         /* Add PV */
491         buflen = strlen(buf);
492
493         strncpy( buf + buflen, ed->pv, sizeof(buf) - buflen );
494
495         buf[ sizeof(buf) - 3 ] = '\0';
496
497         strcat( buf + buflen, "\r\n" );
498
499         /* Update memo */
500         InsertIntoMemo( ed->hMemo, buf );
501     }
502
503     /* Colors */
504     SetEngineColorIcon( ed->hColorIcon, ed->which );
505 }
506
507 LRESULT CALLBACK EngineOutputProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
508 {
509     static SnapData sd;
510
511     switch (message) {
512     case WM_INITDIALOG:
513         if( engineOutputDialog == NULL ) {
514             engineOutputDialog = hDlg;
515
516             RestoreWindowPlacement( hDlg, &wpEngineOutput ); /* Restore window placement */
517
518             ResizeWindowControls( hDlg, windowMode );
519
520             SetEngineState( 0, STATE_IDLE, "" );
521             SetEngineState( 1, STATE_IDLE, "" );
522         }
523
524         return FALSE;
525
526     case WM_COMMAND:
527         switch (LOWORD(wParam)) {
528         case IDOK:
529           EndDialog(hDlg, TRUE);
530           return TRUE;
531
532         case IDCANCEL:
533           EndDialog(hDlg, FALSE);
534           return TRUE;
535
536         default:
537           break;
538         }
539
540         break;
541
542     case WM_GETMINMAXINFO:
543         {
544             MINMAXINFO * mmi = (MINMAXINFO *) lParam;
545
546             mmi->ptMinTrackSize.x = 100;
547             mmi->ptMinTrackSize.y = 160;
548         }
549         break;
550
551     case WM_CLOSE:
552         EngineOutputPopDown();
553         break;
554
555     case WM_SIZE:
556         ResizeWindowControls( hDlg, windowMode );
557         break;
558
559     case WM_ENTERSIZEMOVE:
560         return OnEnterSizeMove( &sd, hDlg, wParam, lParam );
561
562     case WM_SIZING:
563         return OnSizing( &sd, hDlg, wParam, lParam );
564
565     case WM_MOVING:
566         return OnMoving( &sd, hDlg, wParam, lParam );
567
568     case WM_EXITSIZEMOVE:
569         return OnExitSizeMove( &sd, hDlg, wParam, lParam );
570     }
571
572     return FALSE;
573 }
574
575 VOID EngineOutputPopUp()
576 {
577   FARPROC lpProc;
578
579   if( needInit ) {
580       InitializeEngineOutput();
581   }
582
583   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEngineOutput, MF_CHECKED);
584
585   if( engineOutputDialog ) {
586     SendMessage( engineOutputDialog, WM_INITDIALOG, 0, 0 );
587
588     if( ! engineOutputDialogUp ) {
589         ShowWindow(engineOutputDialog, SW_SHOW);
590     }
591   }
592   else {
593     lpProc = MakeProcInstance( (FARPROC) EngineOutputProc, hInst );
594
595     /* Note to self: dialog must have the WS_VISIBLE style set, otherwise it's not shown! */
596     CreateDialog( hInst, MAKEINTRESOURCE(DLG_EngineOutput), hwndMain, (DLGPROC)lpProc );
597
598     FreeProcInstance(lpProc);
599   }
600
601   engineOutputDialogUp = TRUE;
602 }
603
604 VOID EngineOutputPopDown()
605 {
606   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEngineOutput, MF_UNCHECKED);
607
608   if( engineOutputDialog ) {
609       ShowWindow(engineOutputDialog, SW_HIDE);
610   }
611
612   engineOutputDialogUp = FALSE;
613 }
614
615 BOOL EngineOutputIsUp()
616 {
617     return engineOutputDialogUp;
618 }
619
620 VOID EngineOutputUpdate( int which, int depth, unsigned long nodes, int score, int time, char * pv, char * hint )
621 {
622     EngineOutputData ed;
623     BOOL clearMemo = FALSE;
624
625     if( which < 0 || which > 1 || depth < 0 || time < 0 || pv == 0 || *pv == '\0' ) {
626         return;
627     }
628
629     if( engineOutputDialog == NULL ) {
630         return;
631     }
632
633     VerifyDisplayMode();
634
635     ed.which = which;
636     ed.depth = depth;
637     ed.nodes = nodes;
638     ed.score = score;
639     ed.time = time;
640     ed.pv = pv;
641     ed.hint = hint;
642
643     /* Get target control */
644     if( which == 0 ) {
645         ed.hColorIcon = GetDlgItem( engineOutputDialog, IDC_Color1 );
646         ed.hLabel = GetDlgItem( engineOutputDialog, IDC_EngineLabel1 );
647         ed.hStateIcon = GetDlgItem( engineOutputDialog, IDC_StateIcon1 );
648         ed.hStateData = GetDlgItem( engineOutputDialog, IDC_StateData1 );
649         ed.hLabelNPS = GetDlgItem( engineOutputDialog, IDC_Engine1_NPS );
650         ed.hMemo  = GetDlgItem( engineOutputDialog, IDC_EngineMemo1 );
651         ed.name = first.tidy;
652     }
653     else {
654         ed.hColorIcon = GetDlgItem( engineOutputDialog, IDC_Color2 );
655         ed.hLabel = GetDlgItem( engineOutputDialog, IDC_EngineLabel2 );
656         ed.hStateIcon = GetDlgItem( engineOutputDialog, IDC_StateIcon2 );
657         ed.hStateData = GetDlgItem( engineOutputDialog, IDC_StateData2 );
658         ed.hLabelNPS = GetDlgItem( engineOutputDialog, IDC_Engine2_NPS );
659         ed.hMemo  = GetDlgItem( engineOutputDialog, IDC_EngineMemo2 );
660         ed.name = second.tidy;
661     }
662
663     /* Clear memo if needed */
664     if( lastDepth[which] > depth || (lastDepth[which] == depth && depth <= 1) ) {
665         clearMemo = TRUE;
666     }
667
668     if( lastForwardMostMove[which] != forwardMostMove ) {
669         clearMemo = TRUE;
670     }
671
672     if( clearMemo ) {
673         SendMessage( ed.hMemo, WM_SETTEXT, 0, (LPARAM) "" );
674     }
675
676     /* Update */
677     lastDepth[which] = depth;
678     lastForwardMostMove[which] = forwardMostMove;
679
680     if( pv[0] == ' ' ) {
681         if( strncmp( pv, " no PV", 6 ) == 0 ) { /* Hack on hack! :-O */
682             ed.pv = "";
683         }
684     }
685
686     UpdateControls( &ed );
687 }