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