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