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