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