f2c0e569427d8f68709de4f7ec96f2f583a3888c
[xboard.git] / winboard / wengineoutput.c
1 /*\r
2  * wengineoutput.c - split-off front-end of Engine output (PV) by HGM\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 #include "config.h"\r
27 \r
28 #include <windows.h> /* required for all Windows applications */\r
29 #include <richedit.h>\r
30 #include <stdio.h>\r
31 #include <stdlib.h>\r
32 #include <malloc.h>\r
33 #include <commdlg.h>\r
34 #include <dlgs.h>\r
35 \r
36 #include "common.h"\r
37 #include "frontend.h"\r
38 #include "backend.h"\r
39 #include "winboard.h"\r
40 \r
41 #include "wsnap.h"\r
42 #include "engineoutput.h"\r
43 \r
44 /* Module variables */\r
45 int  windowMode = 1;\r
46 static BOOLEAN engineOutputDialogUp = FALSE;\r
47 HICON icons[8]; // [HGM] this front-end array translates back-end icon indicator to handle\r
48 HWND outputField[2][7]; // [HGM] front-end array to translate output field to window handle\r
49 \r
50 // front end\r
51 static HICON LoadIconEx( int id )\r
52 {\r
53     return LoadImage( hInst, MAKEINTRESOURCE(id), IMAGE_ICON, ICON_SIZE, ICON_SIZE, 0 );\r
54 }\r
55 \r
56 // [HGM] the platform-dependent way of indicating where output should go is now all\r
57 // concentrated here, where a table of platform-dependent handles are initialized.\r
58 // This cleanses most other routines of front-end stuff, so they can go into the back end.\r
59 static void InitializeEngineOutput()\r
60 {\r
61         // [HGM] made this into a table, rather than separate global variables\r
62         icons[nColorBlack]   = LoadIconEx( IDI_BLACK_14 );\r
63         icons[nColorWhite]   = LoadIconEx( IDI_WHITE_14 );\r
64         icons[nColorUnknown] = LoadIconEx( IDI_UNKNOWN_14 );\r
65         icons[nClear]        = LoadIconEx( IDI_TRANS_14 );\r
66         icons[nPondering]    = LoadIconEx( IDI_PONDER_14 );\r
67         icons[nThinking]     = LoadIconEx( IDI_CLOCK_14 );\r
68         icons[nAnalyzing]    = LoadIconEx( IDI_ANALYZE2_14 );\r
69 \r
70         // [HGM] also make a table of handles to output controls\r
71         // Note that engineOutputDialog must be defined first!\r
72         outputField[0][nColorIcon] = GetDlgItem( engineOutputDialog, IDC_Color1 );\r
73         outputField[0][nLabel]     = GetDlgItem( engineOutputDialog, IDC_EngineLabel1 );\r
74         outputField[0][nStateIcon] = GetDlgItem( engineOutputDialog, IDC_StateIcon1 );\r
75         outputField[0][nStateData] = GetDlgItem( engineOutputDialog, IDC_StateData1 );\r
76         outputField[0][nLabelNPS]  = GetDlgItem( engineOutputDialog, IDC_Engine1_NPS );\r
77         outputField[0][nMemo]      = GetDlgItem( engineOutputDialog, IDC_EngineMemo1 );\r
78 \r
79         outputField[1][nColorIcon] = GetDlgItem( engineOutputDialog, IDC_Color2 );\r
80         outputField[1][nLabel]     = GetDlgItem( engineOutputDialog, IDC_EngineLabel2 );\r
81         outputField[1][nStateIcon] = GetDlgItem( engineOutputDialog, IDC_StateIcon2 );\r
82         outputField[1][nStateData] = GetDlgItem( engineOutputDialog, IDC_StateData2 );\r
83         outputField[1][nLabelNPS]  = GetDlgItem( engineOutputDialog, IDC_Engine2_NPS );\r
84         outputField[1][nMemo]      = GetDlgItem( engineOutputDialog, IDC_EngineMemo2 );\r
85 }\r
86 \r
87 // front end\r
88 static void SetControlPos( HWND hDlg, int id, int x, int y, int width, int height )\r
89 {\r
90     HWND hControl = GetDlgItem( hDlg, id );\r
91 \r
92     SetWindowPos( hControl, HWND_TOP, x, y, width, height, SWP_NOZORDER );\r
93 }\r
94 \r
95 #define HIDDEN_X    20000\r
96 #define HIDDEN_Y    20000\r
97 \r
98 // front end\r
99 static void HideControl( HWND hDlg, int id )\r
100 {\r
101     HWND hControl = GetDlgItem( hDlg, id );\r
102     RECT rc;\r
103 \r
104     GetWindowRect( hControl, &rc );\r
105 \r
106     /* \r
107         Avoid hiding an already hidden control, because that causes many\r
108         unnecessary WM_ERASEBKGND messages!\r
109     */\r
110     if( rc.left != HIDDEN_X || rc.top != HIDDEN_Y ) {\r
111         SetControlPos( hDlg, id, 20000, 20000, 100, 100 );\r
112     }\r
113 }\r
114 \r
115 // front end, although we might make GetWindowRect front end instead\r
116 static int GetControlWidth( HWND hDlg, int id )\r
117 {\r
118     RECT rc;\r
119 \r
120     GetWindowRect( GetDlgItem( hDlg, id ), &rc );\r
121 \r
122     return rc.right - rc.left;\r
123 }\r
124 \r
125 // front end?\r
126 static int GetControlHeight( HWND hDlg, int id )\r
127 {\r
128     RECT rc;\r
129 \r
130     GetWindowRect( GetDlgItem( hDlg, id ), &rc );\r
131 \r
132     return rc.bottom - rc.top;\r
133 }\r
134 \r
135 static int GetHeaderHeight()\r
136 {\r
137     int result = GetControlHeight( engineOutputDialog, IDC_EngineLabel1 );\r
138 \r
139     if( result < ICON_SIZE ) result = ICON_SIZE;\r
140 \r
141     return result;\r
142 }\r
143 \r
144 // The size calculations should be backend? If setControlPos is a platform-dependent way of doing things,\r
145 // a platform-independent wrapper for it should be supplied.\r
146 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 )\r
147 {\r
148     int label_x = x + ICON_SIZE + H_MARGIN;\r
149     int label_h = GetControlHeight( hDlg, IDC_EngineLabel1 );\r
150     int label_y = y + ICON_SIZE - label_h;\r
151     int nps_w = GetControlWidth( hDlg, IDC_Engine1_NPS );\r
152     int nps_x = clientWidth - H_MARGIN - nps_w;\r
153     int state_data_w = GetControlWidth( hDlg, IDC_StateData1 );\r
154     int state_data_x = nps_x - H_MARGIN - state_data_w;\r
155     int state_icon_x = state_data_x - ICON_SIZE - 2;\r
156     int max_w = clientWidth - 2*H_MARGIN;\r
157     int memo_y = y + ICON_SIZE + LABEL_V_DISTANCE;\r
158 \r
159     SetControlPos( hDlg, idColor, x, y, ICON_SIZE, ICON_SIZE );\r
160     SetControlPos( hDlg, idEngineLabel, label_x, label_y, state_icon_x - label_x, label_h );\r
161     SetControlPos( hDlg, idStateIcon, state_icon_x, y, ICON_SIZE, ICON_SIZE );\r
162     SetControlPos( hDlg, idStateData, state_data_x, label_y, state_data_w, label_h );\r
163     SetControlPos( hDlg, idNPS, nps_x, label_y, nps_w, label_h );\r
164     SetControlPos( hDlg, idMemo, x, memo_y, max_w, memoHeight );\r
165 }\r
166 \r
167 // Also here some of the size calculations should go to the back end, and their actual application to a front-end routine\r
168 void ResizeWindowControls( int mode )\r
169 {\r
170     HWND hDlg = engineOutputDialog; // [HGM] used to be parameter, but routine is called from back-end\r
171     RECT rc;\r
172     int headerHeight = GetHeaderHeight();\r
173 //    int labelHeight = GetControlHeight( hDlg, IDC_EngineLabel1 );\r
174 //    int labelOffset = H_MARGIN + ICON_SIZE + H_MARGIN;\r
175 //    int labelDeltaY = ICON_SIZE - labelHeight;\r
176     int clientWidth;\r
177     int clientHeight;\r
178     int maxControlWidth;\r
179     int npsWidth;\r
180 \r
181     /* Initialize variables */\r
182     GetClientRect( hDlg, &rc );\r
183 \r
184     clientWidth = rc.right - rc.left;\r
185     clientHeight = rc.bottom - rc.top;\r
186 \r
187     maxControlWidth = clientWidth - 2*H_MARGIN;\r
188 \r
189     npsWidth = GetControlWidth( hDlg, IDC_Engine1_NPS );\r
190 \r
191     /* Resize controls */\r
192     if( mode == 0 ) {\r
193         /* One engine */\r
194         PositionControlSet( hDlg, H_MARGIN, V_MARGIN, \r
195             clientWidth, \r
196             clientHeight - V_MARGIN - LABEL_V_DISTANCE - headerHeight- V_MARGIN,\r
197             IDC_Color1, IDC_EngineLabel1, IDC_Engine1_NPS, IDC_EngineMemo1, IDC_StateIcon1, IDC_StateData1 );\r
198 \r
199         /* Hide controls for the second engine */\r
200         HideControl( hDlg, IDC_Color2 );\r
201         HideControl( hDlg, IDC_EngineLabel2 );\r
202         HideControl( hDlg, IDC_StateIcon2 );\r
203         HideControl( hDlg, IDC_StateData2 );\r
204         HideControl( hDlg, IDC_Engine2_NPS );\r
205         HideControl( hDlg, IDC_EngineMemo2 );\r
206         SendDlgItemMessage( hDlg, IDC_EngineMemo2, WM_SETTEXT, 0, (LPARAM) "" );\r
207         /* TODO: we should also hide/disable them!!! what about tab stops?!?! */\r
208     }\r
209     else {\r
210         /* Two engines */\r
211         int memo_h = (clientHeight - headerHeight*2 - V_MARGIN*2 - LABEL_V_DISTANCE*2 - SPLITTER_SIZE) / 2;\r
212         int header1_y = V_MARGIN;\r
213         int header2_y = V_MARGIN + headerHeight + LABEL_V_DISTANCE + memo_h + SPLITTER_SIZE;\r
214 \r
215         PositionControlSet( hDlg, H_MARGIN, header1_y, clientWidth, memo_h,\r
216             IDC_Color1, IDC_EngineLabel1, IDC_Engine1_NPS, IDC_EngineMemo1, IDC_StateIcon1, IDC_StateData1 );\r
217 \r
218         PositionControlSet( hDlg, H_MARGIN, header2_y, clientWidth, memo_h,\r
219             IDC_Color2, IDC_EngineLabel2, IDC_Engine2_NPS, IDC_EngineMemo2, IDC_StateIcon2, IDC_StateData2 );\r
220     }\r
221 \r
222     InvalidateRect( GetDlgItem(hDlg,IDC_EngineMemo1), NULL, FALSE );\r
223     InvalidateRect( GetDlgItem(hDlg,IDC_EngineMemo2), NULL, FALSE );\r
224 }\r
225 \r
226 static int currentPV, highTextStart[2], highTextEnd[2];\r
227 extern RECT boardRect;\r
228 \r
229 VOID\r
230 GetMemoLine(HWND hDlg, int x, int y)\r
231 {       // [HGM] pv: translate click to PV line, and load it for display\r
232         char buf[10000];\r
233         int index, start, end, memo;\r
234         POINT pt;\r
235 \r
236         pt.x = x; pt.y = y;\r
237         memo = currentPV ? IDC_EngineMemo2 : IDC_EngineMemo1;\r
238         index = SendDlgItemMessage( hDlg, memo, EM_CHARFROMPOS, 0, (LPARAM) &pt );\r
239         GetDlgItemText(hDlg, memo, buf, sizeof(buf));\r
240         if(LoadMultiPV(x, y, buf, index, &start, &end)) {\r
241             SetCapture(hDlg);\r
242             SendMessage( outputField[currentPV][nMemo], EM_SETSEL, (WPARAM)start, (LPARAM)end );\r
243             highTextStart[currentPV] = start; highTextEnd[currentPV] = end;\r
244             SetFocus(outputField[currentPV][nMemo]);\r
245         }\r
246 }\r
247 \r
248 // front end. Actual printing of PV lines into the output field\r
249 void InsertIntoMemo( int which, char * text, int where )\r
250 {\r
251     SendMessage( outputField[which][nMemo], EM_SETSEL, where, where ); // [HGM] multivar: choose insertion point\r
252 \r
253     SendMessage( outputField[which][nMemo], EM_REPLACESEL, (WPARAM) FALSE, (LPARAM) text );\r
254     if(where < highTextStart[which]) { // [HGM] multiPVdisplay: move highlighting\r
255         int len = strlen(text);\r
256         highTextStart[which] += len; highTextEnd[which] += len;\r
257         SendMessage( outputField[which][nMemo], EM_SETSEL, highTextStart[which], highTextEnd[which] );\r
258     }\r
259 }\r
260 \r
261 // front end. Associates an icon with an output field ("control" in Windows jargon).\r
262 // [HGM] let it find out the output field from the 'which' number by itself\r
263 void SetIcon( int which, int field, int nIcon )\r
264 {\r
265 \r
266     if( nIcon != 0 ) {\r
267         SendMessage( outputField[which][field], STM_SETICON, (WPARAM) icons[nIcon], 0 );\r
268     }\r
269 }\r
270 \r
271 // front end wrapper for SetWindowText, taking control number in stead of handle\r
272 void DoSetWindowText(int which, int field, char *s_label)\r
273 {\r
274     SetWindowText( outputField[which][field], s_label );\r
275 }\r
276 \r
277 void SetEngineOutputTitle(char *title)\r
278 {\r
279     SetWindowText( engineOutputDialog, title );\r
280 }\r
281 \r
282 // This seems pure front end\r
283 LRESULT CALLBACK EngineOutputProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )\r
284 {\r
285     static SnapData sd;\r
286 \r
287     switch (message) {\r
288     case WM_INITDIALOG:\r
289         if( engineOutputDialog == NULL ) {\r
290             engineOutputDialog = hDlg;\r
291 \r
292             Translate(hDlg, DLG_EngineOutput);\r
293             RestoreWindowPlacement( hDlg, &wpEngineOutput ); /* Restore window placement */\r
294 \r
295             ResizeWindowControls( windowMode );\r
296 \r
297             SendDlgItemMessage( hDlg, IDC_EngineMemo1, EM_SETEVENTMASK, 0, ENM_MOUSEEVENTS );\r
298             SendDlgItemMessage( hDlg, IDC_EngineMemo2, EM_SETEVENTMASK, 0, ENM_MOUSEEVENTS );\r
299 \r
300             /* Set font */\r
301             SendDlgItemMessage( engineOutputDialog, IDC_EngineMemo1, WM_SETFONT, (WPARAM)font[boardSize][MOVEHISTORY_FONT]->hf, MAKELPARAM(TRUE, 0 ));\r
302             SendDlgItemMessage( engineOutputDialog, IDC_EngineMemo2, WM_SETFONT, (WPARAM)font[boardSize][MOVEHISTORY_FONT]->hf, MAKELPARAM(TRUE, 0 ));\r
303 \r
304             SetEngineState( 0, STATE_IDLE, "" );\r
305             SetEngineState( 1, STATE_IDLE, "" );\r
306         }\r
307 \r
308         return FALSE;\r
309 \r
310     case WM_COMMAND:\r
311         switch (LOWORD(wParam)) {\r
312         case IDOK:\r
313           EndDialog(hDlg, TRUE);\r
314           return TRUE;\r
315 \r
316         case IDCANCEL:\r
317           EndDialog(hDlg, FALSE);\r
318           return TRUE;\r
319 \r
320         default:\r
321           break;\r
322         }\r
323 \r
324         break;\r
325 \r
326     case WM_MOUSEMOVE:\r
327         MovePV(LOWORD(lParam) - boardRect.left, HIWORD(lParam) - boardRect.top, boardRect.bottom - boardRect.top);\r
328         break;\r
329 \r
330     case WM_RBUTTONUP:\r
331         ReleaseCapture();\r
332         SendMessage( outputField[currentPV][nMemo], EM_SETSEL, 0, 0 );\r
333         highTextStart[currentPV] = highTextEnd[currentPV] = 0;\r
334         UnLoadPV();\r
335         break;\r
336 \r
337     case WM_NOTIFY:\r
338         if( wParam == IDC_EngineMemo1 || wParam == IDC_EngineMemo2 ) {\r
339             MSGFILTER * lpMF = (MSGFILTER *) lParam;\r
340             if( lpMF->msg == WM_RBUTTONDOWN && (lpMF->wParam & (MK_CONTROL)) == 0 ) {\r
341                 shiftKey = (lpMF->wParam & MK_SHIFT) != 0; // [HGM] remember last shift status\r
342                 currentPV = (wParam == IDC_EngineMemo2);\r
343                 GetMemoLine(hDlg, LOWORD(lpMF->lParam), HIWORD(lpMF->lParam));\r
344             }\r
345         }\r
346         break;\r
347 \r
348     case WM_GETMINMAXINFO:\r
349         {\r
350             MINMAXINFO * mmi = (MINMAXINFO *) lParam;\r
351         \r
352             mmi->ptMinTrackSize.x = 100;\r
353             mmi->ptMinTrackSize.y = 160;\r
354         }\r
355         break;\r
356 \r
357     case WM_CLOSE:\r
358         EngineOutputPopDown();\r
359         break;\r
360 \r
361     case WM_SIZE:\r
362         ResizeWindowControls( windowMode );\r
363         break;\r
364 \r
365     case WM_ENTERSIZEMOVE:\r
366         return OnEnterSizeMove( &sd, hDlg, wParam, lParam );\r
367 \r
368     case WM_SIZING:\r
369         return OnSizing( &sd, hDlg, wParam, lParam );\r
370 \r
371     case WM_MOVING:\r
372         return OnMoving( &sd, hDlg, wParam, lParam );\r
373 \r
374     case WM_EXITSIZEMOVE:\r
375         return OnExitSizeMove( &sd, hDlg, wParam, lParam );\r
376     }\r
377 \r
378     return FALSE;\r
379 }\r
380 \r
381 // front end\r
382 void EngineOutputPopUp()\r
383 {\r
384   FARPROC lpProc;\r
385   static int  needInit = TRUE;\r
386   \r
387   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEngineOutput, MF_CHECKED);\r
388 \r
389   if( engineOutputDialog ) {\r
390     SendMessage( engineOutputDialog, WM_INITDIALOG, 0, 0 );\r
391 \r
392     if( ! engineOutputDialogUp ) {\r
393         ShowWindow(engineOutputDialog, SW_SHOW);\r
394     }\r
395   }\r
396   else {\r
397     lpProc = MakeProcInstance( (FARPROC) EngineOutputProc, hInst );\r
398 \r
399     /* Note to self: dialog must have the WS_VISIBLE style set, otherwise it's not shown! */\r
400     CreateDialog( hInst, MAKEINTRESOURCE(DLG_EngineOutput), hwndMain, (DLGPROC)lpProc );\r
401 \r
402     FreeProcInstance(lpProc);\r
403   }\r
404 \r
405   // [HGM] displaced to after creation of dialog, to allow initialization of output fields\r
406   if( needInit ) {\r
407       InitializeEngineOutput();\r
408       needInit = FALSE;\r
409   }\r
410 \r
411   engineOutputDialogUp = TRUE;\r
412 }\r
413 \r
414 // front end\r
415 void EngineOutputPopDown()\r
416 {\r
417   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEngineOutput, MF_UNCHECKED);\r
418 \r
419   if( engineOutputDialog ) {\r
420       ShowWindow(engineOutputDialog, SW_HIDE);\r
421   }\r
422 \r
423   engineOutputDialogUp = FALSE;\r
424 }\r
425 \r
426 // front end. [HGM] Takes handle of output control from table, so only number is passed\r
427 void DoClearMemo(int which)\r
428 {\r
429         SendMessage( outputField[which][nMemo], WM_SETTEXT, 0, (LPARAM) "" );\r
430 }\r
431 \r
432 // front end (because only other front-end wants to know)\r
433 int EngineOutputIsUp()\r
434 {\r
435     return engineOutputDialogUp;\r
436 }\r
437 \r
438 // front end, to give back-end access to engineOutputDialog\r
439 int EngineOutputDialogExists()\r
440 {\r
441     return engineOutputDialog != NULL;\r
442 }\r