4 * Author: Alessandro Scotti (Dec 2005)
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.
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.
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 * ------------------------------------------------------------------------
24 #include <windows.h> /* required for all Windows applications */
39 // [HGM] define numbers to indicate icons, for referring to them in platform-independent way
42 #define nColorUnknown 3
48 HICON icons[8]; // [HGM] this front-end array translates back-end icon indicator to handle
50 // [HGM] same for output fields (note that there are two of each type, one per color)
58 HWND outputField[2][7]; // [HGM] front-end array to translate output field to window handle
60 void EngineOutputPopUp();
61 void EngineOutputPopDown();
62 int EngineOutputIsUp();
64 #define SHOW_PONDERING
66 /* Imports from backend.c */
67 char * SavePart(char *str);
68 extern int opponentKibitzes;
70 /* Imports from winboard.c */
71 extern HWND engineOutputDialog;
72 extern int engineOutputDialogUp;
74 extern HINSTANCE hInst;
77 extern WindowPlacement wpEngineOutput;
79 /* Module variables */
82 #define LABEL_V_DISTANCE 1 /* Distance between label and memo */
83 #define SPLITTER_SIZE 4 /* Distance between first memo and second label */
87 #define STATE_UNKNOWN -1
88 #define STATE_THINKING 0
90 #define STATE_PONDERING 2
91 #define STATE_ANALYZING 3
93 static int windowMode = 1;
95 static int needInit = TRUE;
97 static int lastDepth[2] = { -1, -1 };
98 static int lastForwardMostMove[2] = { -1, -1 };
99 static int engineState[2] = { -1, -1 };
102 // HWND hColorIcon; // [HGM] the output-control handles are no loger passed,
103 // HWND hLabel; // to give better front-end / back-end separation
104 // HWND hStateIcon; // the front-end routines now get them from a (front-end)
105 // HWND hStateData; // table, indexed by output-field indicators.
120 static VerifyDisplayMode();
121 static void UpdateControls( EngineOutputData * ed );
122 static SetEngineState( int which, int state, char * state_data );
125 static HICON LoadIconEx( int id )
127 return LoadImage( hInst, MAKEINTRESOURCE(id), IMAGE_ICON, ICON_SIZE, ICON_SIZE, 0 );
130 // [HGM] the platform-dependent way of indicating where output should go is now all
131 // concentrated here, where a table of platform-dependent handles are initialized.
132 // This cleanses most other routines of front-end stuff, so they can go into the back end.
133 static void InitializeEngineOutput()
135 // if( needInit ) { // needInit was already tested before call
136 // [HGM] made this into a table, rather than separate global variables
137 icons[nColorBlack] = LoadIconEx( IDI_BLACK_14 );
138 icons[nColorWhite] = LoadIconEx( IDI_WHITE_14 );
139 icons[nColorUnknown] = LoadIconEx( IDI_UNKNOWN_14 );
140 icons[nClear] = LoadIconEx( IDI_TRANS_14 );
141 icons[nPondering] = LoadIconEx( IDI_PONDER_14 );
142 icons[nThinking] = LoadIconEx( IDI_CLOCK_14 );
143 icons[nAnalyzing] = LoadIconEx( IDI_ANALYZE2_14 );
145 // [HGM] also make a table of handles to output controls
146 // Note that engineOutputDialog must be defined first!
147 outputField[0][nColorIcon] = GetDlgItem( engineOutputDialog, IDC_Color1 );
148 outputField[0][nLabel] = GetDlgItem( engineOutputDialog, IDC_EngineLabel1 );
149 outputField[0][nStateIcon] = GetDlgItem( engineOutputDialog, IDC_StateIcon1 );
150 outputField[0][nStateData] = GetDlgItem( engineOutputDialog, IDC_StateData1 );
151 outputField[0][nLabelNPS] = GetDlgItem( engineOutputDialog, IDC_Engine1_NPS );
152 outputField[0][nMemo] = GetDlgItem( engineOutputDialog, IDC_EngineMemo1 );
154 outputField[1][nColorIcon] = GetDlgItem( engineOutputDialog, IDC_Color2 );
155 outputField[1][nLabel] = GetDlgItem( engineOutputDialog, IDC_EngineLabel2 );
156 outputField[1][nStateIcon] = GetDlgItem( engineOutputDialog, IDC_StateIcon2 );
157 outputField[1][nStateData] = GetDlgItem( engineOutputDialog, IDC_StateData2 );
158 outputField[1][nLabelNPS] = GetDlgItem( engineOutputDialog, IDC_Engine2_NPS );
159 outputField[1][nMemo] = GetDlgItem( engineOutputDialog, IDC_EngineMemo2 );
165 static void SetControlPos( HWND hDlg, int id, int x, int y, int width, int height )
167 HWND hControl = GetDlgItem( hDlg, id );
169 SetWindowPos( hControl, HWND_TOP, x, y, width, height, SWP_NOZORDER );
172 #define HIDDEN_X 20000
173 #define HIDDEN_Y 20000
176 static void HideControl( HWND hDlg, int id )
178 HWND hControl = GetDlgItem( hDlg, id );
181 GetWindowRect( hControl, &rc );
184 Avoid hiding an already hidden control, because that causes many
185 unnecessary WM_ERASEBKGND messages!
187 if( rc.left != HIDDEN_X || rc.top != HIDDEN_Y ) {
188 SetControlPos( hDlg, id, 20000, 20000, 100, 100 );
192 // front end, although we might make GetWindowRect front end instead
193 static int GetControlWidth( HWND hDlg, int id )
197 GetWindowRect( GetDlgItem( hDlg, id ), &rc );
199 return rc.right - rc.left;
203 static int GetControlHeight( HWND hDlg, int id )
207 GetWindowRect( GetDlgItem( hDlg, id ), &rc );
209 return rc.bottom - rc.top;
212 static int GetHeaderHeight()
214 int result = GetControlHeight( engineOutputDialog, IDC_EngineLabel1 );
216 if( result < ICON_SIZE ) result = ICON_SIZE;
221 // The size calculations should be backend? If setControlPos is a platform-dependent way of doing things,
222 // a platform-independent wrapper for it should be supplied.
223 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 )
225 int label_x = x + ICON_SIZE + H_MARGIN;
226 int label_h = GetControlHeight( hDlg, IDC_EngineLabel1 );
227 int label_y = y + ICON_SIZE - label_h;
228 int nps_w = GetControlWidth( hDlg, IDC_Engine1_NPS );
229 int nps_x = clientWidth - H_MARGIN - nps_w;
230 int state_data_w = GetControlWidth( hDlg, IDC_StateData1 );
231 int state_data_x = nps_x - H_MARGIN - state_data_w;
232 int state_icon_x = state_data_x - ICON_SIZE - 2;
233 int max_w = clientWidth - 2*H_MARGIN;
234 int memo_y = y + ICON_SIZE + LABEL_V_DISTANCE;
236 SetControlPos( hDlg, idColor, x, y, ICON_SIZE, ICON_SIZE );
237 SetControlPos( hDlg, idEngineLabel, label_x, label_y, state_icon_x - label_x, label_h );
238 SetControlPos( hDlg, idStateIcon, state_icon_x, y, ICON_SIZE, ICON_SIZE );
239 SetControlPos( hDlg, idStateData, state_data_x, label_y, state_data_w, label_h );
240 SetControlPos( hDlg, idNPS, nps_x, label_y, nps_w, label_h );
241 SetControlPos( hDlg, idMemo, x, memo_y, max_w, memoHeight );
244 // Also here some of the size calculations should go to the back end, and their actual application to a front-end routine
245 static void ResizeWindowControls( HWND hDlg, int mode )
248 int headerHeight = GetHeaderHeight();
249 int labelHeight = GetControlHeight( hDlg, IDC_EngineLabel1 );
250 int labelOffset = H_MARGIN + ICON_SIZE + H_MARGIN;
251 int labelDeltaY = ICON_SIZE - labelHeight;
257 /* Initialize variables */
258 GetClientRect( hDlg, &rc );
260 clientWidth = rc.right - rc.left;
261 clientHeight = rc.bottom - rc.top;
263 maxControlWidth = clientWidth - 2*H_MARGIN;
265 npsWidth = GetControlWidth( hDlg, IDC_Engine1_NPS );
267 /* Resize controls */
270 PositionControlSet( hDlg, H_MARGIN, V_MARGIN,
272 clientHeight - V_MARGIN - LABEL_V_DISTANCE - headerHeight- V_MARGIN,
273 IDC_Color1, IDC_EngineLabel1, IDC_Engine1_NPS, IDC_EngineMemo1, IDC_StateIcon1, IDC_StateData1 );
275 /* Hide controls for the second engine */
276 HideControl( hDlg, IDC_Color2 );
277 HideControl( hDlg, IDC_EngineLabel2 );
278 HideControl( hDlg, IDC_StateIcon2 );
279 HideControl( hDlg, IDC_StateData2 );
280 HideControl( hDlg, IDC_Engine2_NPS );
281 HideControl( hDlg, IDC_EngineMemo2 );
282 SendDlgItemMessage( hDlg, IDC_EngineMemo2, WM_SETTEXT, 0, (LPARAM) "" );
283 /* TODO: we should also hide/disable them!!! what about tab stops?!?! */
287 int memo_h = (clientHeight - headerHeight*2 - V_MARGIN*2 - LABEL_V_DISTANCE*2 - SPLITTER_SIZE) / 2;
288 int header1_y = V_MARGIN;
289 int header2_y = V_MARGIN + headerHeight + LABEL_V_DISTANCE + memo_h + SPLITTER_SIZE;
291 PositionControlSet( hDlg, H_MARGIN, header1_y, clientWidth, memo_h,
292 IDC_Color1, IDC_EngineLabel1, IDC_Engine1_NPS, IDC_EngineMemo1, IDC_StateIcon1, IDC_StateData1 );
294 PositionControlSet( hDlg, H_MARGIN, header2_y, clientWidth, memo_h,
295 IDC_Color2, IDC_EngineLabel2, IDC_Engine2_NPS, IDC_EngineMemo2, IDC_StateIcon2, IDC_StateData2 );
298 InvalidateRect( GetDlgItem(hDlg,IDC_EngineMemo1), NULL, FALSE );
299 InvalidateRect( GetDlgItem(hDlg,IDC_EngineMemo2), NULL, FALSE );
302 // front end. Actual printing of PV lines into the output field
303 static void InsertIntoMemo( int which, char * text )
305 SendMessage( outputField[which][nMemo], EM_SETSEL, 0, 0 );
307 SendMessage( outputField[which][nMemo], EM_REPLACESEL, (WPARAM) FALSE, (LPARAM) text );
310 // front end. Associates an icon with an output field ("control" in Windows jargon).
311 // [HGM] let it find out the output field from the 'which' number by itself
312 static void SetIcon( int which, int field, int nIcon )
316 SendMessage( outputField[which][field], STM_SETICON, (WPARAM) icons[nIcon], 0 );
320 // front end wrapper for SetWindowText, taking control number in stead of handle
321 void DoSetWindowText(int which, int field, char *s_label)
323 SetWindowText( outputField[which][field], s_label );
326 // This seems pure front end
327 LRESULT CALLBACK EngineOutputProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
333 if( engineOutputDialog == NULL ) {
334 engineOutputDialog = hDlg;
336 RestoreWindowPlacement( hDlg, &wpEngineOutput ); /* Restore window placement */
338 ResizeWindowControls( hDlg, windowMode );
340 SetEngineState( 0, STATE_IDLE, "" );
341 SetEngineState( 1, STATE_IDLE, "" );
347 switch (LOWORD(wParam)) {
349 EndDialog(hDlg, TRUE);
353 EndDialog(hDlg, FALSE);
362 case WM_GETMINMAXINFO:
364 MINMAXINFO * mmi = (MINMAXINFO *) lParam;
366 mmi->ptMinTrackSize.x = 100;
367 mmi->ptMinTrackSize.y = 160;
372 EngineOutputPopDown();
376 ResizeWindowControls( hDlg, windowMode );
379 case WM_ENTERSIZEMOVE:
380 return OnEnterSizeMove( &sd, hDlg, wParam, lParam );
383 return OnSizing( &sd, hDlg, wParam, lParam );
386 return OnMoving( &sd, hDlg, wParam, lParam );
388 case WM_EXITSIZEMOVE:
389 return OnExitSizeMove( &sd, hDlg, wParam, lParam );
396 void EngineOutputPopUp()
400 CheckMenuItem(GetMenu(hwndMain), IDM_ShowEngineOutput, MF_CHECKED);
402 if( engineOutputDialog ) {
403 SendMessage( engineOutputDialog, WM_INITDIALOG, 0, 0 );
405 if( ! engineOutputDialogUp ) {
406 ShowWindow(engineOutputDialog, SW_SHOW);
410 lpProc = MakeProcInstance( (FARPROC) EngineOutputProc, hInst );
412 /* Note to self: dialog must have the WS_VISIBLE style set, otherwise it's not shown! */
413 CreateDialog( hInst, MAKEINTRESOURCE(DLG_EngineOutput), hwndMain, (DLGPROC)lpProc );
415 FreeProcInstance(lpProc);
418 // [HGM] displaced to after creation of dialog, to allow initialization of output fields
420 InitializeEngineOutput();
424 engineOutputDialogUp = TRUE;
428 void EngineOutputPopDown()
430 CheckMenuItem(GetMenu(hwndMain), IDM_ShowEngineOutput, MF_UNCHECKED);
432 if( engineOutputDialog ) {
433 ShowWindow(engineOutputDialog, SW_HIDE);
436 engineOutputDialogUp = FALSE;
439 // front end. [HGM] Takes handle of output control from table, so only number is passed
440 void DoClearMemo(int which)
442 SendMessage( outputField[which][nMemo], WM_SETTEXT, 0, (LPARAM) "" );
445 //------------------------ pure back-end routines -------------------------------
448 // back end, due to front-end wrapper for SetWindowText, and new SetIcon arguments
449 static SetEngineState( int which, int state, char * state_data )
451 int x_which = 1 - which;
453 if( engineState[ which ] != state ) {
454 engineState[ which ] = state;
458 SetIcon( which, nStateIcon, nThinking );
459 if( engineState[ x_which ] == STATE_THINKING ) {
460 SetEngineState( x_which, STATE_IDLE, "" );
463 case STATE_PONDERING:
464 SetIcon( which, nStateIcon, nPondering );
466 case STATE_ANALYZING:
467 SetIcon( which, nStateIcon, nAnalyzing );
470 SetIcon( which, nStateIcon, nClear );
475 if( state_data != 0 ) {
476 DoSetWindowText( which, nStateData, state_data );
480 // back end, now the front-end wrapper ClearMemo is used, and ed no longer contains handles.
481 void EngineOutputUpdate( FrontEndProgramStats * stats )
484 int clearMemo = FALSE;
489 SetEngineState( 0, STATE_IDLE, "" );
490 SetEngineState( 1, STATE_IDLE, "" );
494 if(gameMode == IcsObserving) return; // [HGM] kibitz: shut up engine if we are observing an ICS game
496 which = stats->which;
497 depth = stats->depth;
499 if( which < 0 || which > 1 || depth < 0 || stats->time < 0 || stats->pv == 0 ) {
503 if( engineOutputDialog == NULL ) {
511 ed.nodes = stats->nodes;
512 ed.score = stats->score;
513 ed.time = stats->time;
515 ed.hint = stats->hint;
516 ed.an_move_index = stats->an_move_index;
517 ed.an_move_count = stats->an_move_count;
519 /* Get target control. [HGM] this is moved to front end, which get them from a table */
521 ed.name = first.tidy;
524 ed.name = second.tidy;
527 /* Clear memo if needed */
528 if( lastDepth[which] > depth || (lastDepth[which] == depth && depth <= 1) ) {
532 if( lastForwardMostMove[which] != forwardMostMove ) {
536 if( clearMemo ) DoClearMemo(which);
539 lastDepth[which] = depth;
540 lastForwardMostMove[which] = forwardMostMove;
542 if( ed.pv != 0 && ed.pv[0] == ' ' ) {
543 if( strncmp( ed.pv, " no PV", 6 ) == 0 ) { /* Hack on hack! :-O */
548 UpdateControls( &ed );
551 #define ENGINE_COLOR_WHITE 'w'
552 #define ENGINE_COLOR_BLACK 'b'
553 #define ENGINE_COLOR_UNKNOWN ' '
556 char GetEngineColor( int which )
558 char result = ENGINE_COLOR_UNKNOWN;
560 if( which == 0 || which == 1 ) {
561 ChessProgramState * cps;
564 case MachinePlaysBlack:
565 case IcsPlayingBlack:
566 result = ENGINE_COLOR_BLACK;
568 case MachinePlaysWhite:
569 case IcsPlayingWhite:
570 result = ENGINE_COLOR_WHITE;
574 result = WhiteOnMove(forwardMostMove) ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;
576 case TwoMachinesPlay:
577 cps = (which == 0) ? &first : &second;
578 result = cps->twoMachinesColor[0];
579 result = result == 'w' ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;
588 char GetActiveEngineColor()
590 char result = ENGINE_COLOR_UNKNOWN;
592 if( gameMode == TwoMachinesPlay ) {
593 result = WhiteOnMove(forwardMostMove) ? ENGINE_COLOR_WHITE : ENGINE_COLOR_BLACK;
600 static int IsEnginePondering( int which )
605 case MachinePlaysBlack:
606 case IcsPlayingBlack:
607 if( WhiteOnMove(forwardMostMove) ) result = TRUE;
609 case MachinePlaysWhite:
610 case IcsPlayingWhite:
611 if( ! WhiteOnMove(forwardMostMove) ) result = TRUE;
613 case TwoMachinesPlay:
614 if( GetActiveEngineColor() != ENGINE_COLOR_UNKNOWN ) {
615 if( GetEngineColor( which ) != GetActiveEngineColor() ) result = TRUE;
624 static void SetDisplayMode( int mode )
626 if( windowMode != mode ) {
629 ResizeWindowControls( engineOutputDialog, mode );
634 static VerifyDisplayMode()
638 /* Get proper mode for current game */
642 case MachinePlaysWhite:
643 case MachinePlaysBlack:
646 case IcsPlayingWhite:
647 case IcsPlayingBlack:
648 mode = appData.zippyPlay && opponentKibitzes; // [HGM] kibitz
650 case TwoMachinesPlay:
658 SetDisplayMode( mode );
661 // back end. Determine what icon to se in the color-icon field, and print it
662 static void SetEngineColorIcon( int which )
664 char color = GetEngineColor(which);
667 if( color == ENGINE_COLOR_BLACK )
669 else if( color == ENGINE_COLOR_WHITE )
672 nicon = nColorUnknown;
674 SetIcon( which, nColorIcon, nicon );
677 #define MAX_NAME_LENGTH 32
679 // pure back end, now SetWindowText is called via wrapper DoSetWindowText
680 static void UpdateControls( EngineOutputData * ed )
682 int isPondering = FALSE;
684 char s_label[MAX_NAME_LENGTH + 32];
686 char * name = ed->name;
689 if( name == 0 || *name == '\0' ) {
693 strncpy( s_label, name, MAX_NAME_LENGTH );
694 s_label[ MAX_NAME_LENGTH-1 ] = '\0';
696 #ifdef SHOW_PONDERING
697 if( IsEnginePondering( ed->which ) ) {
702 if( ed->hint != 0 && *ed->hint != '\0' ) {
703 strncpy( buf, ed->hint, sizeof(buf) );
704 buf[sizeof(buf)-1] = '\0';
706 else if( ed->pv != 0 && *ed->pv != '\0' ) {
707 char * sep = strchr( ed->pv, ' ' );
708 int buflen = sizeof(buf);
711 buflen = sep - ed->pv + 1;
712 if( buflen > sizeof(buf) ) buflen = sizeof(buf);
715 strncpy( buf, ed->pv, buflen );
716 buf[ buflen-1 ] = '\0';
719 SetEngineState( ed->which, STATE_PONDERING, buf );
721 else if( gameMode == TwoMachinesPlay ) {
722 SetEngineState( ed->which, STATE_THINKING, "" );
724 else if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
726 int time_secs = ed->time / 100;
727 int time_mins = time_secs / 60;
731 if( ed->an_move_index != 0 && ed->an_move_count != 0 && *ed->hint != '\0' ) {
734 strncpy( mov, ed->hint, sizeof(mov) );
735 mov[ sizeof(mov)-1 ] = '\0';
737 sprintf( buf, "%d/%d: %s [%02d:%02d:%02d]", ed->an_move_index, ed->an_move_count, mov, time_mins / 60, time_mins % 60, time_secs % 60 );
740 SetEngineState( ed->which, STATE_ANALYZING, buf );
743 SetEngineState( ed->which, STATE_IDLE, "" );
747 DoSetWindowText( ed->which, nLabel, s_label );
751 if( ed->time > 0 && ed->nodes > 0 ) {
752 unsigned long nps_100 = ed->nodes / ed->time;
754 if( nps_100 < 100000 ) {
755 sprintf( s_label, "NPS: %lu", nps_100 * 100 );
758 sprintf( s_label, "NPS: %.1fk", nps_100 / 10.0 );
762 DoSetWindowText( ed->which, nLabelNPS, s_label );
765 if( ed->pv != 0 && *ed->pv != '\0' ) {
771 int time_secs = ed->time / 100;
772 int time_cent = ed->time % 100;
775 if( ed->nodes < 1000000 ) {
776 sprintf( s_nodes, "%lu", ed->nodes );
779 sprintf( s_nodes, "%.1fM", ed->nodes / 1000000.0 );
783 if( ed->score > 0 ) {
784 sprintf( s_score, "+%.2f", ed->score / 100.0 );
787 sprintf( s_score, "%.2f", ed->score / 100.0 );
791 sprintf( s_time, "%d:%02d.%02d", time_secs / 60, time_secs % 60, time_cent );
793 /* Put all together... */
794 sprintf( buf, "%3d\t%s\t%s\t%s\t", ed->depth, s_score, s_nodes, s_time );
797 buflen = strlen(buf);
799 strncpy( buf + buflen, ed->pv, sizeof(buf) - buflen );
801 buf[ sizeof(buf) - 3 ] = '\0';
803 strcat( buf + buflen, "\r\n" );
806 InsertIntoMemo( ed->which, buf );
810 SetEngineColorIcon( ed->which );
814 int EngineOutputIsUp()
816 return engineOutputDialogUp;
819 // [HGM] kibitz: write kibitz line; split window for it if necessary
820 void OutputKibitz(int window, char *text)
822 if(!EngineOutputIsUp()) return;
823 if(!opponentKibitzes) { // on first kibitz of game, clear memos
825 if(gameMode == IcsObserving) DoClearMemo(0);
827 opponentKibitzes = TRUE; // this causes split window DisplayMode in ICS modes.
829 if(gameMode == IcsObserving) {
830 DoSetWindowText(0, nLabel, gameInfo.white);
831 SetIcon( 0, nColorIcon, nColorWhite);
832 SetIcon( 0, nStateIcon, nClear);
834 DoSetWindowText(1, nLabel, gameMode == IcsPlayingBlack ? gameInfo.white : gameInfo.black); // opponent name
835 SetIcon( 1, nColorIcon, gameMode == IcsPlayingBlack ? nColorWhite : nColorBlack);
836 SetIcon( 1, nStateIcon, nClear);
837 InsertIntoMemo(window-1, text);