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