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