301d4f6ed95aae4169bacbedb4561392c39a5f6c
[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 // This seems pure front end\r
278 LRESULT CALLBACK EngineOutputProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )\r
279 {\r
280     static SnapData sd;\r
281 \r
282     switch (message) {\r
283     case WM_INITDIALOG:\r
284         if( engineOutputDialog == NULL ) {\r
285             engineOutputDialog = hDlg;\r
286 \r
287             Translate(hDlg, DLG_EngineOutput);\r
288             RestoreWindowPlacement( hDlg, &wpEngineOutput ); /* Restore window placement */\r
289 \r
290             ResizeWindowControls( windowMode );\r
291 \r
292             SendDlgItemMessage( hDlg, IDC_EngineMemo1, EM_SETEVENTMASK, 0, ENM_MOUSEEVENTS );\r
293             SendDlgItemMessage( hDlg, IDC_EngineMemo2, EM_SETEVENTMASK, 0, ENM_MOUSEEVENTS );\r
294 \r
295             /* Set font */\r
296             SendDlgItemMessage( engineOutputDialog, IDC_EngineMemo1, WM_SETFONT, (WPARAM)font[boardSize][MOVEHISTORY_FONT]->hf, MAKELPARAM(TRUE, 0 ));\r
297             SendDlgItemMessage( engineOutputDialog, IDC_EngineMemo2, WM_SETFONT, (WPARAM)font[boardSize][MOVEHISTORY_FONT]->hf, MAKELPARAM(TRUE, 0 ));\r
298 \r
299             SetEngineState( 0, STATE_IDLE, "" );\r
300             SetEngineState( 1, STATE_IDLE, "" );\r
301         }\r
302 \r
303         return FALSE;\r
304 \r
305     case WM_COMMAND:\r
306         switch (LOWORD(wParam)) {\r
307         case IDOK:\r
308           EndDialog(hDlg, TRUE);\r
309           return TRUE;\r
310 \r
311         case IDCANCEL:\r
312           EndDialog(hDlg, FALSE);\r
313           return TRUE;\r
314 \r
315         default:\r
316           break;\r
317         }\r
318 \r
319         break;\r
320 \r
321     case WM_MOUSEMOVE:\r
322         MovePV(LOWORD(lParam) - boardRect.left, HIWORD(lParam) - boardRect.top, boardRect.bottom - boardRect.top);\r
323         break;\r
324 \r
325     case WM_RBUTTONUP:\r
326         ReleaseCapture();\r
327         SendMessage( outputField[currentPV][nMemo], EM_SETSEL, 0, 0 );\r
328         highTextStart[currentPV] = highTextEnd[currentPV] = 0;\r
329         UnLoadPV();\r
330         break;\r
331 \r
332     case WM_NOTIFY:\r
333         if( wParam == IDC_EngineMemo1 || wParam == IDC_EngineMemo2 ) {\r
334             MSGFILTER * lpMF = (MSGFILTER *) lParam;\r
335             if( lpMF->msg == WM_RBUTTONDOWN && (lpMF->wParam & (MK_CONTROL)) == 0 ) {\r
336                 shiftKey = (lpMF->wParam & MK_SHIFT) != 0; // [HGM] remember last shift status\r
337                 currentPV = (wParam == IDC_EngineMemo2);\r
338                 GetMemoLine(hDlg, LOWORD(lpMF->lParam), HIWORD(lpMF->lParam));\r
339             }\r
340         }\r
341         break;\r
342 \r
343     case WM_GETMINMAXINFO:\r
344         {\r
345             MINMAXINFO * mmi = (MINMAXINFO *) lParam;\r
346         \r
347             mmi->ptMinTrackSize.x = 100;\r
348             mmi->ptMinTrackSize.y = 160;\r
349         }\r
350         break;\r
351 \r
352     case WM_CLOSE:\r
353         EngineOutputPopDown();\r
354         break;\r
355 \r
356     case WM_SIZE:\r
357         ResizeWindowControls( windowMode );\r
358         break;\r
359 \r
360     case WM_ENTERSIZEMOVE:\r
361         return OnEnterSizeMove( &sd, hDlg, wParam, lParam );\r
362 \r
363     case WM_SIZING:\r
364         return OnSizing( &sd, hDlg, wParam, lParam );\r
365 \r
366     case WM_MOVING:\r
367         return OnMoving( &sd, hDlg, wParam, lParam );\r
368 \r
369     case WM_EXITSIZEMOVE:\r
370         return OnExitSizeMove( &sd, hDlg, wParam, lParam );\r
371     }\r
372 \r
373     return FALSE;\r
374 }\r
375 \r
376 // front end\r
377 void EngineOutputPopUp()\r
378 {\r
379   FARPROC lpProc;\r
380   static int  needInit = TRUE;\r
381   \r
382   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEngineOutput, MF_CHECKED);\r
383 \r
384   if( engineOutputDialog ) {\r
385     SendMessage( engineOutputDialog, WM_INITDIALOG, 0, 0 );\r
386 \r
387     if( ! engineOutputDialogUp ) {\r
388         ShowWindow(engineOutputDialog, SW_SHOW);\r
389     }\r
390   }\r
391   else {\r
392     lpProc = MakeProcInstance( (FARPROC) EngineOutputProc, hInst );\r
393 \r
394     /* Note to self: dialog must have the WS_VISIBLE style set, otherwise it's not shown! */\r
395     CreateDialog( hInst, MAKEINTRESOURCE(DLG_EngineOutput), hwndMain, (DLGPROC)lpProc );\r
396 \r
397     FreeProcInstance(lpProc);\r
398   }\r
399 \r
400   // [HGM] displaced to after creation of dialog, to allow initialization of output fields\r
401   if( needInit ) {\r
402       InitializeEngineOutput();\r
403       needInit = FALSE;\r
404   }\r
405 \r
406   engineOutputDialogUp = TRUE;\r
407 }\r
408 \r
409 // front end\r
410 void EngineOutputPopDown()\r
411 {\r
412   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEngineOutput, MF_UNCHECKED);\r
413 \r
414   if( engineOutputDialog ) {\r
415       ShowWindow(engineOutputDialog, SW_HIDE);\r
416   }\r
417 \r
418   engineOutputDialogUp = FALSE;\r
419 }\r
420 \r
421 // front end. [HGM] Takes handle of output control from table, so only number is passed\r
422 void DoClearMemo(int which)\r
423 {\r
424         SendMessage( outputField[which][nMemo], WM_SETTEXT, 0, (LPARAM) "" );\r
425 }\r
426 \r
427 // front end (because only other front-end wants to know)\r
428 int EngineOutputIsUp()\r
429 {\r
430     return engineOutputDialogUp;\r
431 }\r
432 \r
433 // front end, to give back-end access to engineOutputDialog\r
434 int EngineOutputDialogExists()\r
435 {\r
436     return engineOutputDialog != NULL;\r
437 }\r