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