Merge branch 'v4.7.x' to get DIFFSTAT, Changelog, etc up to date in master
[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 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, highTextStart[2], highTextEnd[2];\r
229 extern RECT boardRect;\r
230 \r
231 VOID\r
232 GetMemoLine(HWND hDlg, int x, int y)\r
233 {       // [HGM] pv: translate click to PV line, and load it for display\r
234         char buf[10000];\r
235         int index, start, end, memo;\r
236         POINT pt;\r
237 \r
238         pt.x = x; pt.y = y;\r
239         memo = currentPV ? IDC_EngineMemo2 : IDC_EngineMemo1;\r
240         index = SendDlgItemMessage( hDlg, memo, EM_CHARFROMPOS, 0, (LPARAM) &pt );\r
241         GetDlgItemText(hDlg, memo, buf, sizeof(buf));\r
242         if(LoadMultiPV(x, y, buf, index, &start, &end, currentPV)) {\r
243             SetCapture(hDlg);\r
244             SendMessage( outputField[currentPV][nMemo], EM_SETSEL, (WPARAM)start, (LPARAM)end );\r
245             highTextStart[currentPV] = start; highTextEnd[currentPV] = end;\r
246             SetFocus(outputField[currentPV][nMemo]);\r
247         }\r
248 }\r
249 \r
250 // front end. Actual printing of PV lines into the output field\r
251 void InsertIntoMemo( int which, char * text, int where )\r
252 {\r
253     SendMessage( outputField[which][nMemo], EM_SETSEL, where, where ); // [HGM] multivar: choose insertion point\r
254 \r
255     SendMessage( outputField[which][nMemo], EM_REPLACESEL, (WPARAM) FALSE, (LPARAM) text );\r
256     if(where < highTextStart[which]) { // [HGM] multiPVdisplay: move highlighting\r
257         int len = strlen(text);\r
258         highTextStart[which] += len; highTextEnd[which] += len;\r
259         SendMessage( outputField[which][nMemo], EM_SETSEL, highTextStart[which], highTextEnd[which] );\r
260     }\r
261 }\r
262 \r
263 // front end. Associates an icon with an output field ("control" in Windows jargon).\r
264 // [HGM] let it find out the output field from the 'which' number by itself\r
265 void SetIcon( int which, int field, int nIcon )\r
266 {\r
267 \r
268     if( nIcon != 0 ) {\r
269         SendMessage( outputField[which][field], STM_SETICON, (WPARAM) icons[nIcon], 0 );\r
270     }\r
271 }\r
272 \r
273 // front end wrapper for SetWindowText, taking control number in stead of handle\r
274 void DoSetWindowText(int which, int field, char *s_label)\r
275 {\r
276     SetWindowText( outputField[which][field], s_label );\r
277 }\r
278 \r
279 void SetEngineOutputTitle(char *title)\r
280 {\r
281     SetWindowText( engineOutputDialog, title );\r
282 }\r
283 \r
284 // This seems pure front end\r
285 LRESULT CALLBACK EngineOutputProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )\r
286 {\r
287     static SnapData sd;\r
288 \r
289     switch (message) {\r
290     case WM_INITDIALOG:\r
291         if( engineOutputDialog == NULL ) {\r
292             engineOutputDialog = hDlg;\r
293 \r
294             Translate(hDlg, DLG_EngineOutput);\r
295             RestoreWindowPlacement( hDlg, &wpEngineOutput ); /* Restore window placement */\r
296 \r
297             ResizeWindowControls( windowMode );\r
298 \r
299             SendDlgItemMessage( hDlg, IDC_EngineMemo1, EM_SETEVENTMASK, 0, ENM_MOUSEEVENTS );\r
300             SendDlgItemMessage( hDlg, IDC_EngineMemo2, EM_SETEVENTMASK, 0, ENM_MOUSEEVENTS );\r
301 \r
302             /* Set font */\r
303             SendDlgItemMessage( engineOutputDialog, IDC_EngineMemo1, WM_SETFONT, (WPARAM)font[boardSize][MOVEHISTORY_FONT]->hf, MAKELPARAM(TRUE, 0 ));\r
304             SendDlgItemMessage( engineOutputDialog, IDC_EngineMemo2, WM_SETFONT, (WPARAM)font[boardSize][MOVEHISTORY_FONT]->hf, MAKELPARAM(TRUE, 0 ));\r
305 \r
306             SetEngineState( 0, STATE_IDLE, "" );\r
307             SetEngineState( 1, STATE_IDLE, "" );\r
308         }\r
309 \r
310         return FALSE;\r
311 \r
312     case WM_COMMAND:\r
313         switch (LOWORD(wParam)) {\r
314         case IDOK:\r
315           EndDialog(hDlg, TRUE);\r
316           return TRUE;\r
317 \r
318         case IDCANCEL:\r
319           EndDialog(hDlg, FALSE);\r
320           return TRUE;\r
321 \r
322         default:\r
323           break;\r
324         }\r
325 \r
326         break;\r
327 \r
328     case WM_MOUSEMOVE:\r
329         MovePV(LOWORD(lParam) - boardRect.left, HIWORD(lParam) - boardRect.top, boardRect.bottom - boardRect.top);\r
330         break;\r
331 \r
332     case WM_RBUTTONUP:\r
333         ReleaseCapture();\r
334         SendMessage( outputField[currentPV][nMemo], EM_SETSEL, 0, 0 );\r
335         highTextStart[currentPV] = highTextEnd[currentPV] = 0;\r
336         UnLoadPV();\r
337         break;\r
338 \r
339     case WM_NOTIFY:\r
340         if( wParam == IDC_EngineMemo1 || wParam == IDC_EngineMemo2 ) {\r
341             MSGFILTER * lpMF = (MSGFILTER *) lParam;\r
342             if( lpMF->msg == WM_RBUTTONDOWN && (lpMF->wParam & (MK_CONTROL)) == 0 ) {\r
343                 shiftKey = (lpMF->wParam & MK_SHIFT) != 0; // [HGM] remember last shift status\r
344                 currentPV = (wParam == IDC_EngineMemo2);\r
345                 GetMemoLine(hDlg, LOWORD(lpMF->lParam), HIWORD(lpMF->lParam));\r
346             }\r
347         }\r
348         break;\r
349 \r
350     case WM_GETMINMAXINFO:\r
351         {\r
352             MINMAXINFO * mmi = (MINMAXINFO *) lParam;\r
353         \r
354             mmi->ptMinTrackSize.x = 100;\r
355             mmi->ptMinTrackSize.y = 160;\r
356         }\r
357         break;\r
358 \r
359     case WM_CLOSE:\r
360         EngineOutputPopDown();\r
361         break;\r
362 \r
363     case WM_SIZE:\r
364         ResizeWindowControls( windowMode );\r
365         break;\r
366 \r
367     case WM_ENTERSIZEMOVE:\r
368         return OnEnterSizeMove( &sd, hDlg, wParam, lParam );\r
369 \r
370     case WM_SIZING:\r
371         return OnSizing( &sd, hDlg, wParam, lParam );\r
372 \r
373     case WM_MOVING:\r
374         return OnMoving( &sd, hDlg, wParam, lParam );\r
375 \r
376     case WM_EXITSIZEMOVE:\r
377         return OnExitSizeMove( &sd, hDlg, wParam, lParam );\r
378     }\r
379 \r
380     return FALSE;\r
381 }\r
382 \r
383 // front end\r
384 void EngineOutputPopUp()\r
385 {\r
386   FARPROC lpProc;\r
387   static int  needInit = TRUE;\r
388   \r
389   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEngineOutput, MF_CHECKED);\r
390 \r
391   if( engineOutputDialog ) {\r
392     SendMessage( engineOutputDialog, WM_INITDIALOG, 0, 0 );\r
393 \r
394     if( ! engineOutputDialogUp ) {\r
395         ShowWindow(engineOutputDialog, SW_SHOW);\r
396     }\r
397   }\r
398   else {\r
399     lpProc = MakeProcInstance( (FARPROC) EngineOutputProc, hInst );\r
400 \r
401     /* Note to self: dialog must have the WS_VISIBLE style set, otherwise it's not shown! */\r
402     CreateDialog( hInst, MAKEINTRESOURCE(DLG_EngineOutput), hwndMain, (DLGPROC)lpProc );\r
403 \r
404     FreeProcInstance(lpProc);\r
405   }\r
406 \r
407   // [HGM] displaced to after creation of dialog, to allow initialization of output fields\r
408   if( needInit ) {\r
409       InitializeEngineOutput();\r
410       needInit = FALSE;\r
411   }\r
412 \r
413   engineOutputDialogUp = TRUE;\r
414 }\r
415 \r
416 // front end\r
417 void EngineOutputPopDown()\r
418 {\r
419   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEngineOutput, MF_UNCHECKED);\r
420 \r
421   if( engineOutputDialog ) {\r
422       ShowWindow(engineOutputDialog, SW_HIDE);\r
423   }\r
424 \r
425   engineOutputDialogUp = FALSE;\r
426 }\r
427 \r
428 // front end. [HGM] Takes handle of output control from table, so only number is passed\r
429 void DoClearMemo(int which)\r
430 {\r
431         SendMessage( outputField[which][nMemo], WM_SETTEXT, 0, (LPARAM) "" );\r
432 }\r
433 \r
434 // front end (because only other front-end wants to know)\r
435 int EngineOutputIsUp()\r
436 {\r
437     return engineOutputDialogUp;\r
438 }\r
439 \r
440 // front end, to give back-end access to engineOutputDialog\r
441 int EngineOutputDialogExists()\r
442 {\r
443     return engineOutputDialog != NULL;\r
444 }\r