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