Updated all files to GPL version 3.
[xboard.git] / winboard / whistory.c
1 /*\r
2  * Move history for WinBoard\r
3  *\r
4  * Author: Alessandro Scotti (Dec 2005)\r
5  *\r
6  * ------------------------------------------------------------------------\r
7  *\r
8  * GNU XBoard is free software: you can redistribute it and/or modify\r
9  * it under the terms of the GNU General Public License as published by\r
10  * the Free Software Foundation, either version 3 of the License, or (at\r
11  * your option) any later version.\r
12  *\r
13  * GNU XBoard is distributed in the hope that it will be useful, but\r
14  * WITHOUT ANY WARRANTY; without even the implied warranty of\r
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
16  * General Public License for more details.\r
17  *\r
18  * You should have received a copy of the GNU General Public License\r
19  * along with this program. If not, see http://www.gnu.org/licenses/. \r
20  *\r
21  * ------------------------------------------------------------------------\r
22  ** See the file ChangeLog for a revision history.  */\r
23 \r
24 #include "config.h"\r
25 \r
26 #include <windows.h> /* required for all Windows applications */\r
27 #include <richedit.h>\r
28 #include <stdio.h>\r
29 #include <stdlib.h>\r
30 #include <malloc.h>\r
31 #include <commdlg.h>\r
32 #include <dlgs.h>\r
33 \r
34 #include "common.h"\r
35 #include "winboard.h"\r
36 #include "frontend.h"\r
37 #include "backend.h"\r
38 \r
39 #include "wsnap.h"\r
40 \r
41 VOID MoveHistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current, ChessProgramStats_Move * pvInfo );\r
42 VOID MoveHistoryPopUp();\r
43 VOID MoveHistoryPopDown();\r
44 BOOL MoveHistoryIsUp();\r
45 \r
46 /* Imports from backend.c */\r
47 char * SavePart(char *str);\r
48 \r
49 /* Imports from winboard.c */\r
50 extern HWND moveHistoryDialog;\r
51 extern BOOLEAN moveHistoryDialogUp;\r
52 \r
53 extern HINSTANCE hInst;\r
54 extern HWND hwndMain;\r
55 \r
56 extern WindowPlacement wpMoveHistory;\r
57 \r
58 extern BoardSize boardSize;\r
59 \r
60 /* Module globals */\r
61 typedef char MoveHistoryString[ MOVE_LEN*2 ];\r
62 \r
63 static int lastFirst = 0;\r
64 static int lastLast = 0;\r
65 static int lastCurrent = -1;\r
66 \r
67 static char lastLastMove[ MOVE_LEN ];\r
68 \r
69 static MoveHistoryString * currMovelist;\r
70 static ChessProgramStats_Move * currPvInfo;\r
71 static int currFirst = 0;\r
72 static int currLast = 0;\r
73 static int currCurrent = -1;\r
74 \r
75 typedef struct {\r
76     int memoOffset;\r
77     int memoLength;\r
78 } HistoryMove;\r
79 \r
80 static HistoryMove histMoves[ MAX_MOVES ];\r
81 \r
82 #define WM_REFRESH_HISTORY  (WM_USER+4657)\r
83 \r
84 #define DEFAULT_COLOR       0xFFFFFFFF\r
85 \r
86 #define H_MARGIN            2\r
87 #define V_MARGIN            2\r
88 \r
89 /* Note: in the following code a "Memo" is a Rich Edit control (it's Delphi lingo) */\r
90 \r
91 static VOID HighlightMove( int index, BOOL highlight )\r
92 {\r
93     if( index >= 0 && index < MAX_MOVES ) {\r
94         CHARFORMAT cf;\r
95         HWND hMemo = GetDlgItem( moveHistoryDialog, IDC_MoveHistory );\r
96 \r
97         SendMessage( hMemo, \r
98             EM_SETSEL, \r
99             histMoves[index].memoOffset, \r
100             histMoves[index].memoOffset + histMoves[index].memoLength );\r
101 \r
102 \r
103         /* Set style */\r
104         ZeroMemory( &cf, sizeof(cf) );\r
105 \r
106         cf.cbSize = sizeof(cf);\r
107         cf.dwMask = CFM_BOLD | CFM_COLOR;\r
108 \r
109         if( highlight ) {\r
110             cf.dwEffects |= CFE_BOLD;\r
111             cf.crTextColor = RGB( 0x00, 0x00, 0xFF );\r
112         }\r
113         else {\r
114             cf.dwEffects |= CFE_AUTOCOLOR;\r
115         }\r
116 \r
117         SendMessage( hMemo, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf );\r
118     }\r
119 }\r
120 \r
121 static BOOL OnlyCurrentPositionChanged()\r
122 {\r
123     BOOL result = FALSE;\r
124 \r
125     if( lastFirst >= 0 &&\r
126         lastLast >= lastFirst &&\r
127         lastCurrent >= lastFirst && \r
128         currFirst == lastFirst &&\r
129         currLast == lastLast &&\r
130         currCurrent >= 0 &&\r
131         TRUE )\r
132     {\r
133         result = TRUE;\r
134 \r
135         /* Special case: last move changed */\r
136         if( currCurrent == currLast-1 ) {\r
137             if( strcmp( currMovelist[currCurrent], lastLastMove ) != 0 ) {\r
138                 result = FALSE;\r
139             }\r
140         }\r
141     }\r
142 \r
143     return result;\r
144 }\r
145 \r
146 static BOOL OneMoveAppended()\r
147 {\r
148     BOOL result = FALSE;\r
149 \r
150     if( lastCurrent >= 0 && lastCurrent >= lastFirst && lastLast >= lastFirst &&\r
151         currCurrent >= 0 && currCurrent >= currFirst && currLast >= currFirst &&\r
152         lastFirst == currFirst &&\r
153         lastLast == (currLast-1) &&\r
154         lastCurrent == (currCurrent-1) &&\r
155         currCurrent == (currLast-1) &&\r
156         TRUE )\r
157     {\r
158         result = TRUE;\r
159     }\r
160 \r
161     return result;\r
162 }\r
163 \r
164 static VOID ClearMemo()\r
165 {\r
166     SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, WM_SETTEXT, 0, (LPARAM) "" );\r
167 }\r
168 \r
169 static int AppendToMemo( char * text, DWORD flags, DWORD color )\r
170 {\r
171     CHARFORMAT cf;\r
172 \r
173     HWND hMemo = GetDlgItem( moveHistoryDialog, IDC_MoveHistory );\r
174 \r
175     /* Select end of text */\r
176     int cbTextLen = (int) SendMessage( hMemo, WM_GETTEXTLENGTH, 0, 0 );\r
177 \r
178     SendMessage( hMemo, EM_SETSEL, cbTextLen, cbTextLen );\r
179 \r
180     /* Set style */\r
181     ZeroMemory( &cf, sizeof(cf) );\r
182 \r
183     cf.cbSize = sizeof(cf);\r
184     cf.dwMask = CFM_BOLD | CFM_ITALIC | CFM_COLOR | CFM_UNDERLINE;\r
185     cf.dwEffects = flags;\r
186 \r
187     if( color != DEFAULT_COLOR ) {\r
188         cf.crTextColor = color;\r
189     }\r
190     else {\r
191         cf.dwEffects |= CFE_AUTOCOLOR;\r
192     }\r
193 \r
194     SendMessage( hMemo, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf );\r
195 \r
196     /* Append text */\r
197     SendMessage( hMemo, EM_REPLACESEL, (WPARAM) FALSE, (LPARAM) text );\r
198 \r
199     /* Return offset of appended text */\r
200     return cbTextLen;\r
201 }\r
202 \r
203 static VOID AppendMoveToMemo( int index )\r
204 {\r
205     char buf[64];\r
206     DWORD flags = 0;\r
207     DWORD color = DEFAULT_COLOR;\r
208 \r
209     if( index < 0 || index >= MAX_MOVES ) {\r
210         return;\r
211     }\r
212 \r
213     buf[0] = '\0';\r
214 \r
215     /* Move number */\r
216     if( (index % 2) == 0 ) {\r
217         sprintf( buf, "%d.%s ", (index / 2)+1, index & 1 ? ".." : "" );\r
218         AppendToMemo( buf, CFE_BOLD, DEFAULT_COLOR );\r
219     }\r
220 \r
221     /* Move text */\r
222     strcpy( buf, SavePart( currMovelist[index] ) );\r
223     strcat( buf, " " );\r
224 \r
225     histMoves[index].memoOffset = AppendToMemo( buf, flags, color );\r
226     histMoves[index].memoLength = strlen(buf)-1;\r
227 \r
228     /* PV info (if any) */\r
229     if( appData.showEvalInMoveHistory && currPvInfo[index].depth > 0 ) {\r
230         sprintf( buf, "{%s%.2f/%d} ", \r
231             currPvInfo[index].score >= 0 ? "+" : "",\r
232             currPvInfo[index].score / 100.0,\r
233             currPvInfo[index].depth );\r
234 \r
235         AppendToMemo( buf, flags, \r
236             color == DEFAULT_COLOR ? GetSysColor(COLOR_GRAYTEXT) : color );\r
237     }\r
238 }\r
239 \r
240 static void RefreshMemoContent()\r
241 {\r
242     int i;\r
243 \r
244     ClearMemo();\r
245 \r
246     for( i=currFirst; i<currLast; i++ ) {\r
247         AppendMoveToMemo( i );\r
248     }\r
249 }\r
250 \r
251 static void MemoContentUpdated()\r
252 {\r
253     int caretPos;\r
254 \r
255     HighlightMove( lastCurrent, FALSE );\r
256     HighlightMove( currCurrent, TRUE );\r
257 \r
258     lastFirst = currFirst;\r
259     lastLast = currLast;\r
260     lastCurrent = currCurrent;\r
261     lastLastMove[0] = '\0';\r
262 \r
263     if( lastLast > 0 ) {\r
264         strcpy( lastLastMove, SavePart( currMovelist[lastLast-1] ) );\r
265     }\r
266 \r
267     /* Deselect any text, move caret to end of memo */\r
268     if( currCurrent >= 0 ) {\r
269         caretPos = histMoves[currCurrent].memoOffset + histMoves[currCurrent].memoLength;\r
270     }\r
271     else {\r
272         caretPos = (int) SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, WM_GETTEXTLENGTH, 0, 0 );\r
273     }\r
274 \r
275     SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, EM_SETSEL, caretPos, caretPos );\r
276 \r
277     SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, EM_SCROLLCARET, 0, 0 );\r
278 }\r
279 \r
280 int FindMoveByCharIndex( int char_index )\r
281 {\r
282     int index;\r
283 \r
284     for( index=currFirst; index<currLast; index++ ) {\r
285         if( char_index >= histMoves[index].memoOffset &&\r
286             char_index <  (histMoves[index].memoOffset + histMoves[index].memoLength) )\r
287         {\r
288             return index;\r
289         }\r
290     }\r
291 \r
292     return -1;\r
293 }\r
294 \r
295 LRESULT CALLBACK HistoryDialogProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )\r
296 {\r
297     static SnapData sd;\r
298 \r
299     switch (message) {\r
300     case WM_INITDIALOG:\r
301         if( moveHistoryDialog == NULL ) {\r
302             moveHistoryDialog = hDlg;\r
303 \r
304             /* Enable word wrapping and notifications */\r
305             SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, EM_SETTARGETDEVICE, 0, 0 );\r
306 \r
307             SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, EM_SETEVENTMASK, 0, ENM_MOUSEEVENTS );\r
308 \r
309             /* Set font */\r
310             SendDlgItemMessage( moveHistoryDialog, IDC_MoveHistory, WM_SETFONT, (WPARAM)font[boardSize][MOVEHISTORY_FONT]->hf, MAKELPARAM(TRUE, 0 ));\r
311 \r
312             /* Restore window placement */\r
313             RestoreWindowPlacement( hDlg, &wpMoveHistory );\r
314         }\r
315 \r
316         /* Update memo */\r
317         RefreshMemoContent();\r
318 \r
319         MemoContentUpdated();\r
320 \r
321         return FALSE;\r
322 \r
323     case WM_COMMAND:\r
324         switch (LOWORD(wParam)) {\r
325         case IDOK:\r
326           EndDialog(hDlg, TRUE);\r
327           return TRUE;\r
328 \r
329         case IDCANCEL:\r
330           EndDialog(hDlg, FALSE);\r
331           return TRUE;\r
332 \r
333         default:\r
334           break;\r
335         }\r
336 \r
337         break;\r
338 \r
339     case WM_NOTIFY:\r
340         if( wParam == IDC_MoveHistory ) {\r
341             MSGFILTER * lpMF = (MSGFILTER *) lParam;\r
342 \r
343             if( lpMF->msg == WM_LBUTTONDBLCLK && (lpMF->wParam & (MK_CONTROL | MK_SHIFT)) == 0 ) {\r
344                 POINTL pt;\r
345                 LRESULT index;\r
346 \r
347                 pt.x = LOWORD( lpMF->lParam );\r
348                 pt.y = HIWORD( lpMF->lParam );\r
349 \r
350                 index = SendDlgItemMessage( hDlg, IDC_MoveHistory, EM_CHARFROMPOS, 0, (LPARAM) &pt );\r
351 \r
352                 index = FindMoveByCharIndex( index );\r
353 \r
354                 if( index >= 0 ) {\r
355                     ToNrEvent( index + 1 );\r
356                 }\r
357 \r
358                 /* Zap the message for good: apparently, returning non-zero is not enough */\r
359                 lpMF->msg = WM_USER;\r
360 \r
361                 return TRUE;\r
362             }\r
363         }\r
364         break;\r
365 \r
366     case WM_REFRESH_HISTORY:\r
367         /* Update the GUI */\r
368         if( OnlyCurrentPositionChanged() ) {\r
369             /* Only "cursor" changed, no need to update memo content */\r
370         }\r
371         else if( OneMoveAppended() ) {\r
372             AppendMoveToMemo( currCurrent );\r
373         }\r
374         else {\r
375             RefreshMemoContent();\r
376         }\r
377 \r
378         MemoContentUpdated();\r
379 \r
380         break;\r
381 \r
382     case WM_SIZE:\r
383         SetWindowPos( GetDlgItem( moveHistoryDialog, IDC_MoveHistory ),\r
384             HWND_TOP,\r
385             H_MARGIN, V_MARGIN,\r
386             LOWORD(lParam) - 2*H_MARGIN,\r
387             HIWORD(lParam) - 2*V_MARGIN,\r
388             SWP_NOZORDER );\r
389         break;\r
390 \r
391     case WM_GETMINMAXINFO:\r
392         {\r
393             MINMAXINFO * mmi = (MINMAXINFO *) lParam;\r
394         \r
395             mmi->ptMinTrackSize.x = 100;\r
396             mmi->ptMinTrackSize.y = 100;\r
397         }\r
398         break;\r
399 \r
400     case WM_CLOSE:\r
401         MoveHistoryPopDown();\r
402         break;\r
403 \r
404     case WM_ENTERSIZEMOVE:\r
405         return OnEnterSizeMove( &sd, hDlg, wParam, lParam );\r
406 \r
407     case WM_SIZING:\r
408         return OnSizing( &sd, hDlg, wParam, lParam );\r
409 \r
410     case WM_MOVING:\r
411         return OnMoving( &sd, hDlg, wParam, lParam );\r
412 \r
413     case WM_EXITSIZEMOVE:\r
414         return OnExitSizeMove( &sd, hDlg, wParam, lParam );\r
415     }\r
416 \r
417     return FALSE;\r
418 }\r
419 \r
420 VOID MoveHistoryPopUp()\r
421 {\r
422   FARPROC lpProc;\r
423   \r
424   CheckMenuItem(GetMenu(hwndMain), IDM_ShowMoveHistory, MF_CHECKED);\r
425 \r
426   if( moveHistoryDialog ) {\r
427     SendMessage( moveHistoryDialog, WM_INITDIALOG, 0, 0 );\r
428 \r
429     if( ! moveHistoryDialogUp ) {\r
430         ShowWindow(moveHistoryDialog, SW_SHOW);\r
431     }\r
432   }\r
433   else {\r
434     lpProc = MakeProcInstance( (FARPROC) HistoryDialogProc, hInst );\r
435 \r
436     /* Note to self: dialog must have the WS_VISIBLE style set, otherwise it's not shown! */\r
437     CreateDialog( hInst, MAKEINTRESOURCE(DLG_MoveHistory), hwndMain, (DLGPROC)lpProc );\r
438 \r
439     FreeProcInstance(lpProc);\r
440   }\r
441 \r
442   moveHistoryDialogUp = TRUE;\r
443 }\r
444 \r
445 VOID MoveHistoryPopDown()\r
446 {\r
447   CheckMenuItem(GetMenu(hwndMain), IDM_ShowMoveHistory, MF_UNCHECKED);\r
448 \r
449   if( moveHistoryDialog ) {\r
450       ShowWindow(moveHistoryDialog, SW_HIDE);\r
451   }\r
452 \r
453   moveHistoryDialogUp = FALSE;\r
454 }\r
455 \r
456 VOID MoveHistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current, ChessProgramStats_Move * pvInfo )\r
457 {\r
458     /* [AS] Danger! For now we rely on the movelist parameter being a static variable! */\r
459 \r
460     currMovelist = movelist;\r
461     currFirst = first;\r
462     currLast = last;\r
463     currCurrent = current;\r
464     currPvInfo = pvInfo;\r
465 \r
466     if( moveHistoryDialog ) {\r
467         SendMessage( moveHistoryDialog, WM_REFRESH_HISTORY, 0, 0 );\r
468     }\r
469 }\r
470 \r
471 BOOL MoveHistoryIsUp()\r
472 {\r
473     return moveHistoryDialogUp;\r
474 }\r