refactoring of engineoutput
[xboard.git] / winboard / wengineo.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 // front end. Actual printing of PV lines into the output field\r
227 void InsertIntoMemo( int which, char * text, int where )\r
228 {\r
229     SendMessage( outputField[which][nMemo], EM_SETSEL, where, where ); // [HGM] multivar: choose insertion point\r
230 \r
231     SendMessage( outputField[which][nMemo], EM_REPLACESEL, (WPARAM) FALSE, (LPARAM) text );\r
232 }\r
233 \r
234 // front end. Associates an icon with an output field ("control" in Windows jargon).\r
235 // [HGM] let it find out the output field from the 'which' number by itself\r
236 void SetIcon( int which, int field, int nIcon )\r
237 {\r
238 \r
239     if( nIcon != 0 ) {\r
240         SendMessage( outputField[which][field], STM_SETICON, (WPARAM) icons[nIcon], 0 );\r
241     }\r
242 }\r
243 \r
244 // front end wrapper for SetWindowText, taking control number in stead of handle\r
245 void DoSetWindowText(int which, int field, char *s_label)\r
246 {\r
247     SetWindowText( outputField[which][field], s_label );\r
248 }\r
249 \r
250 // This seems pure front end\r
251 LRESULT CALLBACK EngineOutputProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )\r
252 {\r
253     static SnapData sd;\r
254 \r
255     switch (message) {\r
256     case WM_INITDIALOG:\r
257         if( engineOutputDialog == NULL ) {\r
258             engineOutputDialog = hDlg;\r
259 \r
260             RestoreWindowPlacement( hDlg, &wpEngineOutput ); /* Restore window placement */\r
261 \r
262             ResizeWindowControls( windowMode );\r
263 \r
264             /* Set font */\r
265             SendDlgItemMessage( engineOutputDialog, IDC_EngineMemo1, WM_SETFONT, (WPARAM)font[boardSize][MOVEHISTORY_FONT]->hf, MAKELPARAM(TRUE, 0 ));\r
266             SendDlgItemMessage( engineOutputDialog, IDC_EngineMemo2, WM_SETFONT, (WPARAM)font[boardSize][MOVEHISTORY_FONT]->hf, MAKELPARAM(TRUE, 0 ));\r
267 \r
268             SetEngineState( 0, STATE_IDLE, "" );\r
269             SetEngineState( 1, STATE_IDLE, "" );\r
270         }\r
271 \r
272         return FALSE;\r
273 \r
274     case WM_COMMAND:\r
275         switch (LOWORD(wParam)) {\r
276         case IDOK:\r
277           EndDialog(hDlg, TRUE);\r
278           return TRUE;\r
279 \r
280         case IDCANCEL:\r
281           EndDialog(hDlg, FALSE);\r
282           return TRUE;\r
283 \r
284         default:\r
285           break;\r
286         }\r
287 \r
288         break;\r
289 \r
290     case WM_GETMINMAXINFO:\r
291         {\r
292             MINMAXINFO * mmi = (MINMAXINFO *) lParam;\r
293         \r
294             mmi->ptMinTrackSize.x = 100;\r
295             mmi->ptMinTrackSize.y = 160;\r
296         }\r
297         break;\r
298 \r
299     case WM_CLOSE:\r
300         EngineOutputPopDown();\r
301         break;\r
302 \r
303     case WM_SIZE:\r
304         ResizeWindowControls( windowMode );\r
305         break;\r
306 \r
307     case WM_ENTERSIZEMOVE:\r
308         return OnEnterSizeMove( &sd, hDlg, wParam, lParam );\r
309 \r
310     case WM_SIZING:\r
311         return OnSizing( &sd, hDlg, wParam, lParam );\r
312 \r
313     case WM_MOVING:\r
314         return OnMoving( &sd, hDlg, wParam, lParam );\r
315 \r
316     case WM_EXITSIZEMOVE:\r
317         return OnExitSizeMove( &sd, hDlg, wParam, lParam );\r
318     }\r
319 \r
320     return FALSE;\r
321 }\r
322 \r
323 // front end\r
324 void EngineOutputPopUp()\r
325 {\r
326   FARPROC lpProc;\r
327   static int  needInit = TRUE;\r
328   \r
329   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEngineOutput, MF_CHECKED);\r
330 \r
331   if( engineOutputDialog ) {\r
332     SendMessage( engineOutputDialog, WM_INITDIALOG, 0, 0 );\r
333 \r
334     if( ! engineOutputDialogUp ) {\r
335         ShowWindow(engineOutputDialog, SW_SHOW);\r
336     }\r
337   }\r
338   else {\r
339     lpProc = MakeProcInstance( (FARPROC) EngineOutputProc, hInst );\r
340 \r
341     /* Note to self: dialog must have the WS_VISIBLE style set, otherwise it's not shown! */\r
342     CreateDialog( hInst, MAKEINTRESOURCE(DLG_EngineOutput), hwndMain, (DLGPROC)lpProc );\r
343 \r
344     FreeProcInstance(lpProc);\r
345   }\r
346 \r
347   // [HGM] displaced to after creation of dialog, to allow initialization of output fields\r
348   if( needInit ) {\r
349       InitializeEngineOutput();\r
350       needInit = FALSE;\r
351   }\r
352 \r
353   engineOutputDialogUp = TRUE;\r
354 }\r
355 \r
356 // front end\r
357 void EngineOutputPopDown()\r
358 {\r
359   CheckMenuItem(GetMenu(hwndMain), IDM_ShowEngineOutput, MF_UNCHECKED);\r
360 \r
361   if( engineOutputDialog ) {\r
362       ShowWindow(engineOutputDialog, SW_HIDE);\r
363   }\r
364 \r
365   engineOutputDialogUp = FALSE;\r
366 }\r
367 \r
368 // front end. [HGM] Takes handle of output control from table, so only number is passed\r
369 void DoClearMemo(int which)\r
370 {\r
371         SendMessage( outputField[which][nMemo], WM_SETTEXT, 0, (LPARAM) "" );\r
372 }\r
373 \r
374 // front end (because only other front-end wants to know)\r
375 int EngineOutputIsUp()\r
376 {\r
377     return engineOutputDialogUp;\r
378 }\r
379 \r
380 // front end, to give back-end access to engineOutputDialog\r
381 int EngineOutputDialogExists()\r
382 {\r
383     return engineOutputDialog != NULL;\r
384 }\r